Commit 54459835 authored by Geoff Simmons's avatar Geoff Simmons

Implement handler for /v1/pems (all file info) in the REST API.

parent cffd35e5
/*
* Copyright (c) 2020 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package rest
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"runtime"
"strconv"
"testing"
"time"
"code.uplex.de/k8s/k8s-crt-dnldr/pkg/crt"
"code.uplex.de/k8s/k8s-crt-dnldr/pkg/pem"
"github.com/sirupsen/logrus"
"k8s.io/client-go/kubernetes/fake"
)
var basedir string
func TestMain(m *testing.M) {
_, filename, _, ok := runtime.Caller(0)
if !ok {
fmt.Fprintln(os.Stderr, "Cannot get test directory")
os.Exit(-1)
}
curDir := filepath.Dir(filename)
basedir = filepath.Join(curDir, "testdata")
os.Exit(m.Run())
}
var (
cafeFile = &pem.File{
Namespace: "ns1",
Name: "cafe",
UID: "975d4e4f-9ea9-49d9-a81d-1b4bca92a743",
ResourceVersion: "4711",
Size: 2899,
ModTime: time.Date(2020, 7, 27, 9, 50, 0, 0, time.UTC),
}
barFile = &pem.File{
Namespace: "ns2",
Name: "bar",
UID: "d18974c5-94d7-4e04-b2af-6e9274ad46d8",
ResourceVersion: "0815",
Size: 5313,
ModTime: time.Date(2020, 7, 27, 9, 51, 0, 0, time.UTC),
}
expWhiskeyCrt = pem.Crt{
Version: 3,
SN: "2f:6d:d8:e4:83:f0:e7:44:4d:3a:cc:97:52:0e:18:93:d7:a5:1f:0d",
Issuer: "CN=bar.example.com,O=The Next Whiskey Bar,L=Hamburg,ST=Hamburg,C=DE",
Valid: pem.Validity{
NotBefore: time.Date(2020, 5, 13, 12, 44, 33, 0, time.UTC),
NotAfter: time.Date(2040, 5, 8, 12, 44, 33, 0, time.UTC),
},
Subject: "CN=bar.example.com,O=The Next Whiskey Bar,L=Hamburg,ST=Hamburg,C=DE",
PubKey: pem.PubKeyInfo{
Algorithm: "RSA",
KeyInfo: pem.RSAKeyInfo{
Bits: 4096,
Modulus: "ae:0b:41:e7:dd:4c:ee:d0:d7:87:e1:c5:f5:b0:67:7a:4c:fd:55:02:4b:54:84:a3:6f:9e:7c:5c:75:d6:4c:17:51:8e:21:dd:1d:ee:57:c1:33:c7:b6:9d:bc:59:cc:58:03:6c:c4:0d:6c:35:43:67:56:83:d9:ea:36:75:ec:32:29:fe:80:5b:04:75:1b:67:49:bf:52:51:f8:7e:b4:25:b9:95:f8:45:f6:ce:b5:d1:c6:8e:7b:35:1e:d7:d9:3a:41:3b:a9:6d:49:aa:1b:76:b5:a2:dd:0a:3c:46:a1:bf:2f:25:cf:c4:05:f2:ad:a7:84:db:97:d5:35:ae:59:3d:13:6a:f1:93:52:95:a5:5a:31:80:aa:e0:9e:66:c0:05:80:db:ff:20:3e:2a:c0:ea:1e:63:cb:9d:08:95:79:9d:c2:a1:f9:48:f5:a1:4c:02:67:0d:9f:0d:7c:09:f1:9f:9d:45:b2:77:9b:d5:4a:05:02:4b:26:b0:db:c6:d3:c1:12:6d:5a:15:83:e9:f0:7d:46:eb:36:13:99:39:55:a0:6a:00:be:3d:6c:58:5a:ae:55:78:ce:2b:df:73:6b:dc:5d:d6:a1:d7:f5:b9:f9:5a:e9:fe:f5:bf:3a:0a:54:d6:9e:2c:ef:8e:78:93:22:2d:61:96:f6:18:62:b1:29:5b:47:d6:d3:5a:b5:bd:d7:f9:a6:3b:68:57:d8:4c:08:92:79:e0:ad:a6:43:15:d5:ad:78:b0:0e:81:de:3c:37:7b:bf:94:cf:04:9c:13:df:e5:8e:05:7e:87:f2:4e:b5:bc:4e:7b:16:11:09:92:5e:97:73:8b:c5:f2:8a:ab:d6:2b:74:7d:ad:52:30:93:58:98:59:1f:dd:70:cd:15:8b:72:27:4d:94:52:f4:23:54:3c:c6:5b:b5:0a:27:f8:ea:98:98:4c:19:20:c9:ca:38:32:3d:d4:1f:14:a9:42:75:46:e3:ed:c0:ed:94:f9:6a:57:a6:4b:88:ae:90:e6:48:50:4b:3d:ec:2b:88:44:20:bd:52:02:83:1c:3c:d7:13:71:8d:d1:08:7b:b3:25:a2:ca:fa:ac:cd:da:c2:9c:1f:62:2c:db:2f:e7:67:81:0b:80:c6:d4:3e:b9:0b:dc:18:ff:55:5d:23:e9:35:ea:f2:7e:2e:a5:a1:d9:aa:0a:31:c7:2d:2e:2a:ba:a3:ac:f7:da:e2:5f:7f:71:54:90:1f:2a:f1:b5:90:fc:e7:a6:0f:0b:5f:5a:df:56:5b:7a:b1:47:2f:4d:bc:19:57:37:bc:35:61:e8:d3:96:b0:1e:20:01:1e:85:9b:33:81:b0:5a:0d:05:d4:69:ae:b4:72:78:07",
Exponent: 65537,
},
},
SubjectKeyID: "ee:e0:91:d8:24:ea:d5:18:f9:68:a0:fa:cb:64:bd:93:cc:37:94:86",
AuthorityKeyID: "ee:e0:91:d8:24:ea:d5:18:f9:68:a0:fa:cb:64:bd:93:cc:37:94:86",
Basic: pem.BasicConstraints{
Valid: true,
IsCA: true,
MaxPath: -1,
},
SigInfo: pem.SigInfo{
Algorithm: "sha256WithRSAEncryption",
Signature: "6f:69:40:32:2a:a3:2f:d7:fe:fb:ad:7f:1b:3a:40:52:82:c7:25:d9:3e:74:fc:49:b8:1c:e5:e8:d1:61:53:57:a2:de:7f:72:c8:6c:d3:6e:e7:10:12:7f:f4:d5:5f:d0:a4:7e:ba:f8:d8:22:8f:bd:2d:41:fc:71:76:ec:bf:a4:07:57:26:77:be:81:69:a2:dd:da:6a:89:f5:26:22:c3:0f:32:ec:dd:bc:d0:8c:e8:55:44:dc:ef:69:04:cc:25:54:b5:8f:f2:f7:30:89:a2:56:ed:f3:37:ad:67:40:78:c3:97:1b:6b:44:ea:45:06:12:70:30:80:5a:6a:a7:45:41:85:df:b2:03:4a:5b:cd:dd:b3:67:a0:63:af:f0:f5:45:b2:fb:30:b6:7d:65:a4:82:ae:c8:2c:8e:d7:00:a4:ec:13:e4:76:5b:4b:5f:2b:31:7b:69:be:71:f2:1b:a2:c7:42:04:aa:42:2a:3f:a2:b2:8d:45:aa:b3:d5:5d:aa:07:89:51:c6:bd:cf:42:df:ab:e0:7b:d8:0d:85:1e:bd:b1:ba:de:3d:b4:2b:10:1d:66:2a:9d:c7:55:fd:fc:aa:9d:26:96:29:ce:70:d6:f0:75:15:71:c1:a2:b7:dd:0e:e4:0c:ed:b0:ec:92:fd:a2:e7:05:81:df:30:fc:73:6a:35:4f:c7:89:e9:8a:3c:a8:d7:ae:b2:46:40:64:03:96:db:ed:18:48:67:58:ba:e8:f1:ec:f4:6d:96:6a:00:d4:a7:58:b6:5d:5e:92:57:f3:82:e1:6f:9f:1b:32:ec:41:1b:d8:e7:51:cd:b0:72:63:1b:8e:22:12:a6:af:28:1a:16:a3:50:00:13:9e:47:3f:07:c0:75:bc:76:7b:de:53:52:74:85:0b:eb:55:40:a3:b9:4e:66:b6:16:e8:f4:86:87:54:22:c4:76:bb:0f:53:62:77:98:5c:8f:a6:9a:ca:63:4a:ca:2e:c4:4a:3e:57:4e:56:ea:25:a0:5b:fb:cc:de:54:e4:7a:e0:7c:49:04:52:46:d0:5c:d1:8e:4f:d3:85:e5:4b:f1:a3:b5:63:b7:23:f3:0a:0f:71:61:89:8f:46:49:84:16:3a:92:e1:0d:e3:89:ef:ec:d6:ec:5e:48:96:e2:12:75:c0:75:50:81:62:cd:b1:06:02:c3:21:12:de:92:20:96:20:64:a2:0e:78:b8:53:06:8e:9c:9e:50:97:80:4c:17:19:a6:4b:f3:b6:35:d2:f4:6a:d5:42:12:3a:2f:78:c4:80:dd:02:e2:c6:b4:4a:0b:d7:fc:d4:7f:2f:9d:8a:05:25:16:6f:83:d7:f9:56:16:1f:0d:35:9c",
},
PEM: `-----BEGIN CERTIFICATE-----
MIIFtTCCA52gAwIBAgIUL23Y5IPw50RNOsyXUg4Yk9elHw0wDQYJKoZIhvcNAQEL
BQAwajELMAkGA1UEBhMCREUxEDAOBgNVBAgMB0hhbWJ1cmcxEDAOBgNVBAcMB0hh
bWJ1cmcxHTAbBgNVBAoMFFRoZSBOZXh0IFdoaXNrZXkgQmFyMRgwFgYDVQQDDA9i
YXIuZXhhbXBsZS5jb20wHhcNMjAwNTEzMTI0NDMzWhcNNDAwNTA4MTI0NDMzWjBq
MQswCQYDVQQGEwJERTEQMA4GA1UECAwHSGFtYnVyZzEQMA4GA1UEBwwHSGFtYnVy
ZzEdMBsGA1UECgwUVGhlIE5leHQgV2hpc2tleSBCYXIxGDAWBgNVBAMMD2Jhci5l
eGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK4LQefd
TO7Q14fhxfWwZ3pM/VUCS1SEo2+efFx11kwXUY4h3R3uV8Ezx7advFnMWANsxA1s
NUNnVoPZ6jZ17DIp/oBbBHUbZ0m/UlH4frQluZX4RfbOtdHGjns1HtfZOkE7qW1J
qht2taLdCjxGob8vJc/EBfKtp4Tbl9U1rlk9E2rxk1KVpVoxgKrgnmbABYDb/yA+
KsDqHmPLnQiVeZ3CoflI9aFMAmcNnw18CfGfnUWyd5vVSgUCSyaw28bTwRJtWhWD
6fB9Rus2E5k5VaBqAL49bFharlV4zivfc2vcXdah1/W5+Vrp/vW/OgpU1p4s7454
kyItYZb2GGKxKVtH1tNatb3X+aY7aFfYTAiSeeCtpkMV1a14sA6B3jw3e7+UzwSc
E9/ljgV+h/JOtbxOexYRCZJel3OLxfKKq9YrdH2tUjCTWJhZH91wzRWLcidNlFL0
I1Q8xlu1Cif46piYTBkgyco4Mj3UHxSpQnVG4+3A7ZT5alemS4iukOZIUEs97CuI
RCC9UgKDHDzXE3GN0Qh7syWiyvqszdrCnB9iLNsv52eBC4DG1D65C9wY/1VdI+k1
6vJ+LqWh2aoKMcctLiq6o6z32uJff3FUkB8q8bWQ/OemDwtfWt9WW3qxRy9NvBlX
N7w1YejTlrAeIAEehZszgbBaDQXUaa60cngHAgMBAAGjUzBRMB0GA1UdDgQWBBTu
4JHYJOrVGPlooPrLZL2TzDeUhjAfBgNVHSMEGDAWgBTu4JHYJOrVGPlooPrLZL2T
zDeUhjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBvaUAyKqMv
1/77rX8bOkBSgscl2T50/Em4HOXo0WFTV6Lef3LIbNNu5xASf/TVX9Ckfrr42CKP
vS1B/HF27L+kB1cmd76BaaLd2mqJ9SYiww8y7N280IzoVUTc72kEzCVUtY/y9zCJ
olbt8zetZ0B4w5cba0TqRQYScDCAWmqnRUGF37IDSlvN3bNnoGOv8PVFsvswtn1l
pIKuyCyO1wCk7BPkdltLXysxe2m+cfIbosdCBKpCKj+iso1FqrPVXaoHiVHGvc9C
36vge9gNhR69sbrePbQrEB1mKp3HVf38qp0mlinOcNbwdRVxwaK33Q7kDO2w7JL9
oucFgd8w/HNqNU/HiemKPKjXrrJGQGQDltvtGEhnWLro8ez0bZZqANSnWLZdXpJX
84Lhb58bMuxBG9jnUc2wcmMbjiISpq8oGhajUAATnkc/B8B1vHZ73lNSdIUL61VA
o7lOZrYW6PSGh1QixHa7D1Nid5hcj6aaymNKyi7ESj5XTlbqJaBb+8zeVOR64HxJ
BFJG0FzRjk/TheVL8aO1Y7cj8woPcWGJj0ZJhBY6kuEN44nv7NbsXkiW4hJ1wHVQ
gWLNsQYCwyES3pIgliBkog54uFMGjpyeUJeATBcZpkvztjXS9GrVQhI6L3jEgN0C
4sa0SgvX/NR/L52KBSUWb4PX+VYWHw01nA==
-----END CERTIFICATE-----
`,
}
expCafeCrt = pem.Crt{
Version: 1,
SN: "76:fc:10:dd:25:d1:0c:22:47:87:8a:28:85:ba:87:5e:f6:cf:e2:6b",
Issuer: "CN=cafe.example.com,O=Green Midget Cafe,L=Hamburg,ST=Hamburg,C=DE",
Valid: pem.Validity{
NotBefore: time.Date(2020, 5, 4, 17, 9, 59, 0, time.UTC),
NotAfter: time.Date(2120, 4, 10, 17, 9, 59, 0, time.UTC),
},
Subject: "CN=cafe.example.com,O=Green Midget Cafe,L=Hamburg,ST=Hamburg,C=DE",
PubKey: pem.PubKeyInfo{
Algorithm: "RSA",
KeyInfo: pem.RSAKeyInfo{
Bits: 2048,
Modulus: "b6:cd:07:0a:ae:9f:ab:d8:2f:d2:e1:1a:de:23:ab:b9:9d:c6:9d:cb:5d:0a:87:86:8e:b4:10:e0:58:76:71:0a:0c:9b:f8:b7:a6:40:d4:b7:ac:76:a4:b4:d0:87:19:4d:de:81:f7:58:4a:35:6b:6e:2b:ff:74:bc:5d:fc:ba:5d:fd:0f:b6:2f:9d:0e:20:aa:03:03:1d:ea:f7:ae:e7:a4:48:b5:53:0e:d6:99:b1:d7:00:e3:18:54:ee:a6:5a:67:3d:1b:73:f0:42:ac:68:a6:1f:8d:97:75:05:a6:dd:86:45:a1:b0:0f:60:de:79:ec:f3:e9:6a:78:d6:ef:79:b8:64:ee:18:af:f6:5a:60:0d:60:27:06:27:be:86:2a:1a:9f:85:90:74:f6:66:d7:f6:c0:34:7e:a8:fa:85:83:bb:5a:3a:19:da:e4:fe:26:a3:da:94:7b:dd:54:c5:b1:1d:94:12:64:93:a5:76:b7:e5:6e:ff:9e:c9:a0:00:32:1c:e1:1a:2f:53:ef:61:a3:52:38:fd:f4:b2:8e:05:65:65:50:73:db:dc:8b:68:3c:8d:ba:86:65:8f:33:61:bc:6a:aa:d7:05:ba:26:af:ef:d6:e0:4e:3f:13:47:d6:4d:d9:40:99:78:8e:51:b4:b6:ca:00:4a:79:bb:9a:bf:61:7b",
Exponent: 65537,
},
},
Basic: pem.BasicConstraints{
Valid: false,
IsCA: false,
MaxPath: -1,
},
SigInfo: pem.SigInfo{
Algorithm: "sha256WithRSAEncryption",
Signature: "4c:34:25:58:31:37:65:68:d6:93:8c:9e:42:ce:79:3d:19:df:ae:4d:22:05:98:e0:9e:b5:33:ad:de:5d:17:7b:46:d2:4f:16:e8:4f:a3:b3:f6:71:43:30:fd:80:d8:74:4a:fc:64:e9:db:97:9c:f8:49:35:8a:20:88:17:8e:d1:72:c1:a9:09:db:a0:fb:1e:30:41:53:f7:a7:16:a4:27:fa:1d:14:dd:51:79:91:99:0a:33:70:06:5b:71:95:15:f0:26:10:2b:2b:93:d6:99:0a:69:98:c6:4f:ad:6d:54:61:6f:32:c7:e9:b9:51:11:33:3b:35:a0:6b:a1:08:96:1a:4c:11:f8:0d:6b:40:4d:08:bf:f6:80:f6:b3:d8:ca:4a:5e:ae:56:7b:dd:c8:0d:ed:8e:d2:79:51:ae:b5:68:0c:75:36:18:53:21:4e:95:92:2e:49:68:68:cc:c8:32:57:0a:47:42:06:c9:ec:c6:f7:06:95:82:c9:62:4b:4d:1b:60:aa:7c:79:12:79:4a:a0:94:62:f0:8c:b8:0a:63:a3:2c:d6:37:6c:39:f6:9e:ad:e7:11:3b:2a:d6:f4:72:68:59:83:cd:c8:48:f4:c1:94:6a:95:27:11:74:72:52:e8:6f:99:d6:c9:eb:ad:5c:d9:0f:fc:a3:a2:df:c3:c8",
},
PEM: `-----BEGIN CERTIFICATE-----
MIIDWTCCAkECFHb8EN0l0QwiR4eKKIW6h172z+JrMA0GCSqGSIb3DQEBCwUAMGgx
CzAJBgNVBAYTAkRFMRAwDgYDVQQIDAdIYW1idXJnMRAwDgYDVQQHDAdIYW1idXJn
MRowGAYDVQQKDBFHcmVlbiBNaWRnZXQgQ2FmZTEZMBcGA1UEAwwQY2FmZS5leGFt
cGxlLmNvbTAgFw0yMDA1MDQxNzA5NTlaGA8yMTIwMDQxMDE3MDk1OVowaDELMAkG
A1UEBhMCREUxEDAOBgNVBAgMB0hhbWJ1cmcxEDAOBgNVBAcMB0hhbWJ1cmcxGjAY
BgNVBAoMEUdyZWVuIE1pZGdldCBDYWZlMRkwFwYDVQQDDBBjYWZlLmV4YW1wbGUu
Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts0HCq6fq9gv0uEa
3iOruZ3GnctdCoeGjrQQ4Fh2cQoMm/i3pkDUt6x2pLTQhxlN3oH3WEo1a24r/3S8
Xfy6Xf0Pti+dDiCqAwMd6veu56RItVMO1pmx1wDjGFTuplpnPRtz8EKsaKYfjZd1
BabdhkWhsA9g3nns8+lqeNbvebhk7hiv9lpgDWAnBie+hioan4WQdPZm1/bANH6o
+oWDu1o6Gdrk/iaj2pR73VTFsR2UEmSTpXa35W7/nsmgADIc4RovU+9ho1I4/fSy
jgVlZVBz29yLaDyNuoZljzNhvGqq1wW6Jq/v1uBOPxNH1k3ZQJl4jlG0tsoASnm7
mr9hewIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBMNCVYMTdlaNaTjJ5Cznk9Gd+u
TSIFmOCetTOt3l0Xe0bSTxboT6Oz9nFDMP2A2HRK/GTp25ec+Ek1iiCIF47RcsGp
Cdug+x4wQVP3pxakJ/odFN1ReZGZCjNwBltxlRXwJhArK5PWmQppmMZPrW1UYW8y
x+m5UREzOzWga6EIlhpMEfgNa0BNCL/2gPaz2MpKXq5We93IDe2O0nlRrrVoDHU2
GFMhTpWSLkloaMzIMlcKR0IGyezG9waVgsliS00bYKp8eRJ5SqCUYvCMuApjoyzW
N2w59p6t5xE7Ktb0cmhZg83ISPTBlGqVJxF0clLob5nWyeutXNkP/KOi38PI
-----END CERTIFICATE-----
`,
}
expCafeInfo = pem.FileInfo{
K8sSecret: pem.Secret{
Namespace: "ns1",
Name: "cafe",
UID: "975d4e4f-9ea9-49d9-a81d-1b4bca92a743",
Version: "4711",
},
Meta: pem.FileMeta{
Size: 2899,
ModTime: time.Date(2020, 7, 27, 9, 50, 0, 0, time.UTC),
},
Cert: expCafeCrt,
}
expBarInfo = pem.FileInfo{
K8sSecret: pem.Secret{
Namespace: "ns2",
Name: "bar",
UID: "d18974c5-94d7-4e04-b2af-6e9274ad46d8",
Version: "0815",
},
Meta: pem.FileMeta{
Size: 5313,
ModTime: time.Date(2020, 7, 27, 9, 51, 0, 0, time.UTC),
},
Cert: expWhiskeyCrt,
}
)
func TestAllPems(t *testing.T) {
client := fake.NewSimpleClientset()
lister := setupSecretLister(client)
getter := crt.NewGetter(lister)
files, err := pem.NewFiles(basedir, -1, getter)
if err != nil {
t.Fatalf("NewFiles(): %v", err)
}
files.Files["ns1/cafe"] = cafeFile
files.Files["ns2/bar"] = barFile
hndlr := &pemsHndlr{
log: &logrus.Logger{Out: ioutil.Discard},
files: files,
crtGetter: getter,
}
req := httptest.NewRequest(http.MethodGet, "/v1/pems", nil)
rr := httptest.NewRecorder()
hndlr.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("GET /v1/pems status: got %d want %d", rr.Code,
http.StatusOK)
}
var allInfo []pem.FileInfo
if err = json.Unmarshal(rr.Body.Bytes(), &allInfo); err != nil {
t.Errorf("GET /v1/pems unmarshal: %v", err)
}
if len(allInfo) != 2 {
t.Fatalf("GET /v1/pems len(slice) got %d want 2", len(allInfo))
}
cafeFound, barFound := false, false
for _, info := range allInfo {
secret := info.K8sSecret
if secret.Namespace == "ns1" && secret.Name == "cafe" {
cafeFound = true
if !reflect.DeepEqual(info, expCafeInfo) {
t.Errorf("GET /v1/pems cafe got %#v want %#v",
info, expCafeInfo)
}
} else if secret.Namespace == "ns2" && secret.Name == "bar" {
barFound = true
if !reflect.DeepEqual(info, expBarInfo) {
t.Errorf("GET /v1/pems bar got %#v want %#v",
info, expBarInfo)
}
} else {
t.Errorf("GET /v1/pems unexpected: %#v", info)
}
}
if !cafeFound {
t.Errorf("GET /v1/pems did not return cafe: %#v", allInfo)
}
if !barFound {
t.Errorf("GET /v1/pems did not return bar: %#v", allInfo)
}
if rr.Header().Get("Content-Length") != strconv.Itoa(rr.Body.Len()) {
t.Errorf("GET /v1/pems Content-Length: got %s want %d",
rr.Header().Get("Content-Length"), rr.Body.Len())
}
if rr.Header().Get("Content-Type") != jsonContentType {
t.Errorf("GET /v1/pems Content-Type: got %s want %s",
rr.Header().Get("Content-Type"), jsonContentType)
}
}
func TestAllPems405(t *testing.T) {
client := fake.NewSimpleClientset()
lister := setupSecretLister(client)
getter := crt.NewGetter(lister)
files, err := pem.NewFiles(basedir, -1, getter)
if err != nil {
t.Fatalf("NewFiles(): %v", err)
}
hndlr := &pemsHndlr{
log: &logrus.Logger{Out: ioutil.Discard},
files: files,
crtGetter: getter,
}
methods := []string{
http.MethodPost, http.MethodPut, http.MethodPatch,
http.MethodDelete, http.MethodConnect, http.MethodOptions,
http.MethodTrace, "DEADBEEF",
}
for _, method := range methods {
req := httptest.NewRequest(method, "/v1/pems", nil)
rr := httptest.NewRecorder()
hndlr.ServeHTTP(rr, req)
if rr.Code != http.StatusMethodNotAllowed {
t.Errorf("%s /v1/pems status: got %d want %d",
method, rr.Code, http.StatusMethodNotAllowed)
}
if rr.Result().Header.Get("Allow") != allowRdonly {
t.Errorf("%s /v1/pems Allow: got %s want %s",
method, rr.Header().Get("Allow"),
allowRdonly)
}
if rr.Result().Header.Get("Content-Length") != "0" {
t.Errorf("%s /v1/pems Content-Length: got %s want 0",
method, rr.Header().Get("Content-Length"))
}
}
}
......@@ -62,11 +62,11 @@ type ErrorDetails struct {
var (
pemsRegex = regexp.MustCompile("^" + pemsPfx + "([^/]+)/([^/]+)$")
allowedHealthz = map[string]struct{}{
allowedRdonly = map[string]struct{}{
http.MethodGet: struct{}{},
http.MethodHead: struct{}{},
}
allowHealthz = "GET, HEAD"
allowRdonly = "GET, HEAD"
allowedPems = map[string]struct{}{
http.MethodGet: struct{}{},
......@@ -77,8 +77,9 @@ var (
}
allowPems = "GET, HEAD, PUT, POST, DELETE"
errCtr = uint64(0)
jsonContentType = "application/json"
problemContentType = "application/problem+json"
errCtr = uint64(0)
errPemsPattern = ErrorDetails{
Type: "/errors/pems/urlPattern",
......@@ -116,6 +117,16 @@ var (
Title: "Error writing PEM file",
Detail: "",
}
errPemsReadErr = ErrorDetails{
Type: "/errors/pems/read/error",
Title: "Error reading PEM file",
Detail: "",
}
errPemsJSONMarshal = ErrorDetails{
Type: "/errors/pems/marshal",
Title: "Error marshaling JSON from PEM file",
Detail: "",
}
)
// Problem Details object per RFC7807
......@@ -169,9 +180,9 @@ func (h *healthzHndlr) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
if req.URL.Path != healthzPath {
status = http.StatusNotFound
} else if _, ok := allowedHealthz[req.Method]; !ok {
} else if _, ok := allowedRdonly[req.Method]; !ok {
status = http.StatusMethodNotAllowed
resp.Header().Set("Allow", allowHealthz)
resp.Header().Set("Allow", allowRdonly)
} else {
status = http.StatusNoContent
resp.Header().Del("Content-Length")
......@@ -231,9 +242,36 @@ func (h *pemsHndlr) allPems(
req *http.Request,
now time.Time,
) {
resp.Header().Set("Content-Length", "0")
resp.WriteHeader(http.StatusNotImplemented)
reqLog(h.log, req, now, http.StatusNotImplemented, 0)
if _, ok := allowedRdonly[req.Method]; !ok {
resp.Header().Set("Allow", allowRdonly)
resp.Header().Set("Content-Length", "0")
resp.WriteHeader(http.StatusMethodNotAllowed)
reqLog(h.log, req, now, http.StatusMethodNotAllowed, 0)
}
// XXX check Accept
// XXX I-M-S and I-N-M
allInfo, err := h.files.GetAllFileInfo()
if err != nil {
h.errorResponse(resp, req, now, http.StatusInternalServerError,
errPemsReadErr, err)
return
}
jsonBytes, err := json.Marshal(allInfo)
if err != nil {
h.errorResponse(resp, req, now, http.StatusInternalServerError,
errPemsJSONMarshal, err)
return
}
// XXX LastModified and ETag
resp.Header().Set("Content-Type", jsonContentType)
resp.Header().Set("Content-Length", strconv.Itoa(len(jsonBytes)))
n, err := resp.Write(jsonBytes)
if err != nil {
h.log.Errorf("%s %s: cannot write body: %v", req.Method,
req.RequestURI, err)
}
reqLog(h.log, req, now, http.StatusOK, n)
}
func (h *pemsHndlr) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
......
......@@ -128,10 +128,10 @@ func TestHealthz405(t *testing.T) {
t.Errorf("%s /v1/healthz status: got %d want %d",
method, rr.Code, http.StatusMethodNotAllowed)
}
if rr.Result().Header.Get("Allow") != allowHealthz {
if rr.Result().Header.Get("Allow") != allowRdonly {
t.Errorf("%s /v1/healthz Allow: got %s want %s",
method, rr.Result().Header.Get("Allow"),
allowHealthz)
allowRdonly)
}
if rr.Result().Header.Get("Content-Length") != "0" {
t.Errorf("%s /v1/healthz Content-Length: got %s want 0",
......@@ -1025,49 +1025,6 @@ func TestPem405(t *testing.T) {
}
}
func TestAllPem(t *testing.T) {
client := fake.NewSimpleClientset()
lister := setupSecretLister(client)
getter := crt.NewGetter(lister)
base, err := getTempDir()
if err != nil {
t.Fatalf("ioutil.TempDir(): %+v", err)
}
defer os.RemoveAll(base)
files, err := pem.NewFiles(base, -1, getter)
if err != nil {
t.Fatalf("NewFiles(): %v", err)
}
hndlr := &pemsHndlr{
log: &logrus.Logger{Out: ioutil.Discard},
files: files,
crtGetter: getter,
}
methods := []string{
http.MethodGet, http.MethodHead, http.MethodPost,
http.MethodPut, http.MethodPatch, http.MethodDelete,
http.MethodConnect, http.MethodOptions, http.MethodTrace,
"DEADBEEF",
}
for _, method := range methods {
req := httptest.NewRequest(method, "/v1/pems", nil)
rr := httptest.NewRecorder()
hndlr.ServeHTTP(rr, req)
if rr.Code != http.StatusNotImplemented {
t.Errorf("%s /v1/pems status: got %d want %d",
method, rr.Code, http.StatusNotImplemented)
}
if rr.Result().Header.Get("Content-Length") != "0" {
t.Errorf("%s /v1/pems Content-Length: got %s want 0",
method, rr.Result().Header.
Get("Content-Length"))
}
}
}
func TestErrors(t *testing.T) {
client := fake.NewSimpleClientset()
lister := setupSecretLister(client)
......
-----BEGIN CERTIFICATE-----
MIIDWTCCAkECFHb8EN0l0QwiR4eKKIW6h172z+JrMA0GCSqGSIb3DQEBCwUAMGgx
CzAJBgNVBAYTAkRFMRAwDgYDVQQIDAdIYW1idXJnMRAwDgYDVQQHDAdIYW1idXJn
MRowGAYDVQQKDBFHcmVlbiBNaWRnZXQgQ2FmZTEZMBcGA1UEAwwQY2FmZS5leGFt
cGxlLmNvbTAgFw0yMDA1MDQxNzA5NTlaGA8yMTIwMDQxMDE3MDk1OVowaDELMAkG
A1UEBhMCREUxEDAOBgNVBAgMB0hhbWJ1cmcxEDAOBgNVBAcMB0hhbWJ1cmcxGjAY
BgNVBAoMEUdyZWVuIE1pZGdldCBDYWZlMRkwFwYDVQQDDBBjYWZlLmV4YW1wbGUu
Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts0HCq6fq9gv0uEa
3iOruZ3GnctdCoeGjrQQ4Fh2cQoMm/i3pkDUt6x2pLTQhxlN3oH3WEo1a24r/3S8
Xfy6Xf0Pti+dDiCqAwMd6veu56RItVMO1pmx1wDjGFTuplpnPRtz8EKsaKYfjZd1
BabdhkWhsA9g3nns8+lqeNbvebhk7hiv9lpgDWAnBie+hioan4WQdPZm1/bANH6o
+oWDu1o6Gdrk/iaj2pR73VTFsR2UEmSTpXa35W7/nsmgADIc4RovU+9ho1I4/fSy
jgVlZVBz29yLaDyNuoZljzNhvGqq1wW6Jq/v1uBOPxNH1k3ZQJl4jlG0tsoASnm7
mr9hewIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBMNCVYMTdlaNaTjJ5Cznk9Gd+u
TSIFmOCetTOt3l0Xe0bSTxboT6Oz9nFDMP2A2HRK/GTp25ec+Ek1iiCIF47RcsGp
Cdug+x4wQVP3pxakJ/odFN1ReZGZCjNwBltxlRXwJhArK5PWmQppmMZPrW1UYW8y
x+m5UREzOzWga6EIlhpMEfgNa0BNCL/2gPaz2MpKXq5We93IDe2O0nlRrrVoDHU2
GFMhTpWSLkloaMzIMlcKR0IGyezG9waVgsliS00bYKp8eRJ5SqCUYvCMuApjoyzW
N2w59p6t5xE7Ktb0cmhZg83ISPTBlGqVJxF0clLob5nWyeutXNkP/KOi38PI
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAts0HCq6fq9gv0uEa3iOruZ3GnctdCoeGjrQQ4Fh2cQoMm/i3
pkDUt6x2pLTQhxlN3oH3WEo1a24r/3S8Xfy6Xf0Pti+dDiCqAwMd6veu56RItVMO
1pmx1wDjGFTuplpnPRtz8EKsaKYfjZd1BabdhkWhsA9g3nns8+lqeNbvebhk7hiv
9lpgDWAnBie+hioan4WQdPZm1/bANH6o+oWDu1o6Gdrk/iaj2pR73VTFsR2UEmST
pXa35W7/nsmgADIc4RovU+9ho1I4/fSyjgVlZVBz29yLaDyNuoZljzNhvGqq1wW6
Jq/v1uBOPxNH1k3ZQJl4jlG0tsoASnm7mr9hewIDAQABAoIBAES7vsQTeNIijYjb
P0D7ZJx8aKv4RVmqL7wElLvmR1KllqwmztbiVZlibZHssuO5bgAWGizGamOkn0KE
YDduyZyBhKDaMlGXkpVjXKJ20vsiWHxlaJTkYWwYV0tU1A8UuvDNG8DhMPaAUCjr
JAMmBPFxySPsBF5itefYgkJBfvXi7sobaCM6A75D+dBLMeq2q+YbIQH/cAojHYfV
7ypyQ1QaY+wsDiCM6n9Qjk4krmHZ/z39y8mO71ytFcMfJJad8LKM5J4p9Qu99qeb
IRDOT/Sb9QXLXWTeCDv5JWPYyFH2u3e/8GsvQLbXYYbfWLNoU6RDaFSc2wmkOwUH
U8pSCDECgYEA3KIQcme//6B2jP31Coa2f8hsENd0nL+EDR9erXLSUga2l0YNPJZj
W6VnNdaeGq92B7Wxgj+dSeeSBdIRhXwABOHHjruG+gotdRRyoO1ldw7mJjN/q3Wx
A1fpJ+J00S1ZO1FbukKZmR7smTS7i73a8V7At3dyjCG6WxErP3N5NM8CgYEA1Bp5
yYIH8oJmPsuJt501k9nU4SdxxQJpb6uZ9QCBqbEsGkWE3vtLErlU8Rnm2HuirMvD
8Q3OsuoupdCTChrJJ04oL/2r60oTGapeDe4BuRM+DRAZ2trCwXy3nT26bZ/DJtur
Hqvt0tey9ee9MiVHWF2biZejd+KMUxPCCoZVS5UCgYEApbz8m+SCH3Yb+DgB7oFZ
8M3PGCuxxto7SVxKVANQKRwv551Q7jWOt9adnJz3Mdai1JHRoaVF87GISOUQEnUe
0owEy5zlfUlN8oiEv4z1zqUbkJDZFCUZ7wgH9tUvqb7mLCAmxtmm5paLZ19sj0H0
iaMDJA8PtmLTyfswwL5uy5MCgYEArdBMgU+nx5oIw+j0IJ4aK+FUzHYQi4vgb3zG
m7ogh7kDFTxnGHwCF4P9Ed9SB5G5y7ToC4BvJLs4IvX7qUouEaHA2SMeYaDAakXs
8albjBkyvm21Yl3nP7w+lALj5bYIrK1TW701FZVhuJaBurhF8So0rdqwQSxMJkCI
wSs4dskCgYBr1LO3GINSwGHt73ueZDtnvFvO+EFDaOFFbsEd14O1mluM4+WrIZky
inZCvygJWzgHF9LCOpoAZxHykMNrEomidpxViAlpBzb/C5CnpzlfiVBqLN3NvOxG
zdkoq6BiZnznsVgoHyP7TQlUX94ahVT01yZ0njPk2aYVipPWUoHQMQ==
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIFtTCCA52gAwIBAgIUL23Y5IPw50RNOsyXUg4Yk9elHw0wDQYJKoZIhvcNAQEL
BQAwajELMAkGA1UEBhMCREUxEDAOBgNVBAgMB0hhbWJ1cmcxEDAOBgNVBAcMB0hh
bWJ1cmcxHTAbBgNVBAoMFFRoZSBOZXh0IFdoaXNrZXkgQmFyMRgwFgYDVQQDDA9i
YXIuZXhhbXBsZS5jb20wHhcNMjAwNTEzMTI0NDMzWhcNNDAwNTA4MTI0NDMzWjBq
MQswCQYDVQQGEwJERTEQMA4GA1UECAwHSGFtYnVyZzEQMA4GA1UEBwwHSGFtYnVy
ZzEdMBsGA1UECgwUVGhlIE5leHQgV2hpc2tleSBCYXIxGDAWBgNVBAMMD2Jhci5l
eGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK4LQefd
TO7Q14fhxfWwZ3pM/VUCS1SEo2+efFx11kwXUY4h3R3uV8Ezx7advFnMWANsxA1s
NUNnVoPZ6jZ17DIp/oBbBHUbZ0m/UlH4frQluZX4RfbOtdHGjns1HtfZOkE7qW1J
qht2taLdCjxGob8vJc/EBfKtp4Tbl9U1rlk9E2rxk1KVpVoxgKrgnmbABYDb/yA+
KsDqHmPLnQiVeZ3CoflI9aFMAmcNnw18CfGfnUWyd5vVSgUCSyaw28bTwRJtWhWD
6fB9Rus2E5k5VaBqAL49bFharlV4zivfc2vcXdah1/W5+Vrp/vW/OgpU1p4s7454
kyItYZb2GGKxKVtH1tNatb3X+aY7aFfYTAiSeeCtpkMV1a14sA6B3jw3e7+UzwSc
E9/ljgV+h/JOtbxOexYRCZJel3OLxfKKq9YrdH2tUjCTWJhZH91wzRWLcidNlFL0
I1Q8xlu1Cif46piYTBkgyco4Mj3UHxSpQnVG4+3A7ZT5alemS4iukOZIUEs97CuI
RCC9UgKDHDzXE3GN0Qh7syWiyvqszdrCnB9iLNsv52eBC4DG1D65C9wY/1VdI+k1
6vJ+LqWh2aoKMcctLiq6o6z32uJff3FUkB8q8bWQ/OemDwtfWt9WW3qxRy9NvBlX
N7w1YejTlrAeIAEehZszgbBaDQXUaa60cngHAgMBAAGjUzBRMB0GA1UdDgQWBBTu
4JHYJOrVGPlooPrLZL2TzDeUhjAfBgNVHSMEGDAWgBTu4JHYJOrVGPlooPrLZL2T
zDeUhjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBvaUAyKqMv
1/77rX8bOkBSgscl2T50/Em4HOXo0WFTV6Lef3LIbNNu5xASf/TVX9Ckfrr42CKP
vS1B/HF27L+kB1cmd76BaaLd2mqJ9SYiww8y7N280IzoVUTc72kEzCVUtY/y9zCJ
olbt8zetZ0B4w5cba0TqRQYScDCAWmqnRUGF37IDSlvN3bNnoGOv8PVFsvswtn1l
pIKuyCyO1wCk7BPkdltLXysxe2m+cfIbosdCBKpCKj+iso1FqrPVXaoHiVHGvc9C
36vge9gNhR69sbrePbQrEB1mKp3HVf38qp0mlinOcNbwdRVxwaK33Q7kDO2w7JL9
oucFgd8w/HNqNU/HiemKPKjXrrJGQGQDltvtGEhnWLro8ez0bZZqANSnWLZdXpJX
84Lhb58bMuxBG9jnUc2wcmMbjiISpq8oGhajUAATnkc/B8B1vHZ73lNSdIUL61VA
o7lOZrYW6PSGh1QixHa7D1Nid5hcj6aaymNKyi7ESj5XTlbqJaBb+8zeVOR64HxJ
BFJG0FzRjk/TheVL8aO1Y7cj8woPcWGJj0ZJhBY6kuEN44nv7NbsXkiW4hJ1wHVQ
gWLNsQYCwyES3pIgliBkog54uFMGjpyeUJeATBcZpkvztjXS9GrVQhI6L3jEgN0C
4sa0SgvX/NR/L52KBSUWb4PX+VYWHw01nA==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
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
sv2NYF2XBWr0oPa9xPvwOZMWFcKSRzw=
-----END PRIVATE KEY-----
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment