├── .gitignore
├── README.md
├── buf.go
├── conn.go
└── main.go
/.gitignore:
--------------------------------------------------------------------------------
1 | pg_check_cert.*
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pg-check-cert: Simple command line utility that can be used to monitor postgresql certificates
2 |
3 | This script connects to a postgresql instance, checks the certificate and displays the amount of days left before it expires.
4 | It's intended to be used for monitoring your postgresql certificates, using a monitoring tool like [Zabbix](http://www.zabbix.com/) or [Nagios](https://www.nagios.org/).
5 |
6 | ## Why openssl is not enough
7 | I used to monitor my postgresql certificates using `openssl`. Unfortunately, the `openssl s_client` option does not support the postgresql handshake, and can therefore only look at the `.crt` file to monitor the expiration date.
8 | As postgresql needs to be restarted after the `.crt` file was replaced, the actual file might be updated, but postgresql is still using the old certificate in-memory, until the server is restarted (as of postgresql-9.5, `reload` is not sufficient to read in the new certificate).
9 |
10 | ## Installation
11 | Precompiled versions (linux-amd64, osx-amd64) are available on the [release page](https://github.com/chr4/pg-check-cert/releases).
12 | Download the file, extract it and move it to e.g. `/usr/local/bin`.
13 |
14 | ## Build
15 | ```shell
16 | go build -o pg-check-cert *.go
17 | ```
18 |
19 | ## Usage
20 | ```shell
21 | pg-check-cert localhost:5432
22 | ```
23 |
24 | ## Thanks
25 | - [thusoy/postgres-migm](https://github.com/thusoy/postgres-mitm/blob/master/postgres_get_server_cert.py) for the inspiration.
26 | - `buf.go` and `conn.go` are taken from [lib/pq](https://github.com/lib/pq/), see copyright notice in the respective files.
27 |
--------------------------------------------------------------------------------
/buf.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake
3 | * Mizerany
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | * SOFTWARE.
22 | */
23 |
24 | package main
25 |
26 | import (
27 | "bytes"
28 | "encoding/binary"
29 |
30 | "github.com/lib/pq/oid"
31 | )
32 |
33 | type readBuf []byte
34 |
35 | func (b *readBuf) int32() (n int) {
36 | n = int(int32(binary.BigEndian.Uint32(*b)))
37 | *b = (*b)[4:]
38 | return
39 | }
40 |
41 | func (b *readBuf) oid() (n oid.Oid) {
42 | n = oid.Oid(binary.BigEndian.Uint32(*b))
43 | *b = (*b)[4:]
44 | return
45 | }
46 |
47 | // N.B: this is actually an unsigned 16-bit integer, unlike int32
48 | func (b *readBuf) int16() (n int) {
49 | n = int(binary.BigEndian.Uint16(*b))
50 | *b = (*b)[2:]
51 | return
52 | }
53 |
54 | func (b *readBuf) string() string {
55 | i := bytes.IndexByte(*b, 0)
56 | if i < 0 {
57 | panic("invalid message format; expected string terminator")
58 | }
59 | s := (*b)[:i]
60 | *b = (*b)[i+1:]
61 | return string(s)
62 | }
63 |
64 | func (b *readBuf) next(n int) (v []byte) {
65 | v = (*b)[:n]
66 | *b = (*b)[n:]
67 | return
68 | }
69 |
70 | func (b *readBuf) byte() byte {
71 | return b.next(1)[0]
72 | }
73 |
74 | type writeBuf struct {
75 | buf []byte
76 | pos int
77 | }
78 |
79 | func (b *writeBuf) int32(n int) {
80 | x := make([]byte, 4)
81 | binary.BigEndian.PutUint32(x, uint32(n))
82 | b.buf = append(b.buf, x...)
83 | }
84 |
85 | func (b *writeBuf) int16(n int) {
86 | x := make([]byte, 2)
87 | binary.BigEndian.PutUint16(x, uint16(n))
88 | b.buf = append(b.buf, x...)
89 | }
90 |
91 | func (b *writeBuf) string(s string) {
92 | b.buf = append(b.buf, (s + "\000")...)
93 | }
94 |
95 | func (b *writeBuf) byte(c byte) {
96 | b.buf = append(b.buf, c)
97 | }
98 |
99 | func (b *writeBuf) bytes(v []byte) {
100 | b.buf = append(b.buf, v...)
101 | }
102 |
103 | func (b *writeBuf) wrap() []byte {
104 | p := b.buf[b.pos:]
105 | binary.BigEndian.PutUint32(p, uint32(len(p)))
106 | return b.buf
107 | }
108 |
109 | func (b *writeBuf) next(c byte) {
110 | p := b.buf[b.pos:]
111 | binary.BigEndian.PutUint32(p, uint32(len(p)))
112 | b.pos = len(b.buf) + 1
113 | b.buf = append(b.buf, c, 0, 0, 0, 0)
114 | }
115 |
--------------------------------------------------------------------------------
/conn.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake
3 | * Mizerany
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | * SOFTWARE.
22 | */
23 |
24 | package main
25 |
26 | import (
27 | "bufio"
28 | "net"
29 | "time"
30 | )
31 |
32 | type parameterStatus struct {
33 | // server version in the same format as server_version_num, or 0 if
34 | // unavailable
35 | serverVersion int
36 |
37 | // the current location based on the TimeZone value of the session, if
38 | // available
39 | currentLocation *time.Location
40 | }
41 |
42 | type transactionStatus byte
43 |
44 | const (
45 | txnStatusIdle transactionStatus = 'I'
46 | txnStatusIdleInTransaction transactionStatus = 'T'
47 | txnStatusInFailedTransaction transactionStatus = 'E'
48 | )
49 |
50 | type conn struct {
51 | c net.Conn
52 | buf *bufio.Reader
53 | namei int
54 | scratch [512]byte
55 | txnStatus transactionStatus
56 |
57 | parameterStatus parameterStatus
58 |
59 | saveMessageType byte
60 | saveMessageBuffer []byte
61 |
62 | // If true, this connection is bad and all public-facing functions should
63 | // return ErrBadConn.
64 | bad bool
65 |
66 | // If set, this connection should never use the binary format when
67 | // receiving query results from prepared statements. Only provided for
68 | // debugging.
69 | disablePreparedBinaryResult bool
70 |
71 | // Whether to always send []byte parameters over as binary. Enables single
72 | // round-trip mode for non-prepared Query calls.
73 | binaryParameters bool
74 | }
75 |
76 | func (c *conn) writeBuf(b byte) *writeBuf {
77 | c.scratch[0] = b
78 | return &writeBuf{
79 | buf: c.scratch[:5],
80 | pos: 1,
81 | }
82 | }
83 |
84 | func (cn *conn) sendStartupPacket(m *writeBuf) {
85 | // sanity check
86 | if m.buf[0] != 0 {
87 | panic("oops")
88 | }
89 |
90 | _, err := cn.c.Write((m.wrap())[1:])
91 | if err != nil {
92 | panic(err)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /* pg_check_cert - Panic when your postgresql certificate is about to expire
2 | * Copyright (C) 2016 Chris Aumann
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "crypto/tls"
22 | "fmt"
23 | "io"
24 | "net"
25 | "os"
26 | "time"
27 | )
28 |
29 | func main() {
30 | if len(os.Args) != 2 {
31 | fmt.Printf("Usage: %s \n", os.Args[0])
32 | os.Exit(1)
33 | }
34 |
35 | pgHost := os.Args[1]
36 |
37 | var err error
38 | cn := &conn{}
39 |
40 | // TODO:
41 | cn.c, err = net.Dial("tcp", pgHost)
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | w := cn.writeBuf(0)
47 | w.int32(80877103)
48 | cn.sendStartupPacket(w)
49 |
50 | b := cn.scratch[:1]
51 | _, err = io.ReadFull(cn.c, b)
52 | if err != nil {
53 | panic(err)
54 | }
55 |
56 | if b[0] != 'S' {
57 | panic("SSL not supported")
58 | }
59 |
60 | tlsConf := tls.Config{}
61 | tlsConf.InsecureSkipVerify = true
62 | client := tls.Client(cn.c, &tlsConf)
63 |
64 | expiresIn := cn.expiresIn(client, &tlsConf)
65 | fmt.Println(expiresIn)
66 | }
67 |
68 | // Check how many days the certificate is still valid
69 | func (cn *conn) expiresIn(client *tls.Conn, tlsConf *tls.Config) int {
70 | err := client.Handshake()
71 | if err != nil {
72 | panic(err)
73 | }
74 | certs := client.ConnectionState().PeerCertificates
75 |
76 | expiresIn := certs[0].NotAfter.Sub(time.Now())
77 |
78 | // Convert days to int
79 | return int(expiresIn.Hours() / 24)
80 | }
81 |
--------------------------------------------------------------------------------