mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-26 23:52:21 +00:00
Compare commits
794 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b458cd43b9 | ||
|
|
07a3faf449 | ||
|
|
1a59b6a686 | ||
|
|
eb891d77a5 | ||
|
|
572474605f | ||
|
|
a36688573b | ||
|
|
acc6c50e48 | ||
|
|
eaa87c7628 | ||
|
|
d6e61012c6 | ||
|
|
cc1f0b8db9 | ||
|
|
90e88bb83c | ||
|
|
2ad181c7dd | ||
|
|
ee881aa406 | ||
|
|
c74dea6405 | ||
|
|
2cdb55e7da | ||
|
|
24b0ddf600 | ||
|
|
cab9ac8368 | ||
|
|
793b8b9260 | ||
|
|
4071b48f01 | ||
|
|
46e61bdea9 | ||
|
|
52341f4e49 | ||
|
|
cd982655a2 | ||
|
|
311bb05993 | ||
|
|
0ec5e57114 | ||
|
|
63779ddac2 | ||
|
|
4de8004094 | ||
|
|
0b72f7084c | ||
|
|
132d2aac72 | ||
|
|
2d4f4e4efd | ||
|
|
c40bca5e65 | ||
|
|
019750a292 | ||
|
|
9e5d4ae51c | ||
|
|
5b9831d319 | ||
|
|
fb8083d024 | ||
|
|
a3f7afaec4 | ||
|
|
f5cf3276d5 | ||
|
|
d5337c9c19 | ||
|
|
c624846eaa | ||
|
|
03f19da21c | ||
|
|
8fe635e7ce | ||
|
|
2fa81546f3 | ||
|
|
43485563ff | ||
|
|
5621c1161a | ||
|
|
79fd8e2901 | ||
|
|
e0886c6948 | ||
|
|
f5cc2f20f7 | ||
|
|
8fd77b72df | ||
|
|
8ecf18521c | ||
|
|
96c705bf94 | ||
|
|
d0d20e00e4 | ||
|
|
53348b8464 | ||
|
|
13daf59217 | ||
|
|
9ebf3a5b92 | ||
|
|
6af1aaeb20 | ||
|
|
6b4fbb6e0e | ||
|
|
edf4ffb018 | ||
|
|
721526b7e7 | ||
|
|
91681b9368 | ||
|
|
3c6f97a457 | ||
|
|
12cbd744b7 | ||
|
|
103538858f | ||
|
|
bdabdf0f42 | ||
|
|
25d20d4081 | ||
|
|
dc24397df4 | ||
|
|
c0874706d9 | ||
|
|
07066e020d | ||
|
|
3f0753ec5a | ||
|
|
15bc6a4a67 | ||
|
|
ce82d799c9 | ||
|
|
a453522d81 | ||
|
|
51c527a965 | ||
|
|
ae7aac020a | ||
|
|
17e8faa0fe | ||
|
|
6639ce2a1f | ||
|
|
53597bb824 | ||
|
|
0e54ba1a20 | ||
|
|
b07a4131e5 | ||
|
|
2cffea5880 | ||
|
|
5f34efc0b7 | ||
|
|
cf471d6422 | ||
|
|
3592f80457 | ||
|
|
488f08dd6e | ||
|
|
cd25cb89c4 | ||
|
|
57fb085bef | ||
|
|
8d12c1b674 | ||
|
|
9c5adad062 | ||
|
|
4649b8e0e4 | ||
|
|
48c5a625a5 | ||
|
|
42bd385cbd | ||
|
|
bedf4e5a39 | ||
|
|
2715741c2c | ||
|
|
d20b2056f2 | ||
|
|
a9b054e2f4 | ||
|
|
051a228a8c | ||
|
|
d162e294ed | ||
|
|
636d2014a7 | ||
|
|
f981f63b90 | ||
|
|
b5be8c6c9b | ||
|
|
ff4ceb5d0b | ||
|
|
d24f82fff5 | ||
|
|
0ac37fd169 | ||
|
|
bcfddec0a2 | ||
|
|
b1310877d8 | ||
|
|
f64779cba7 | ||
|
|
c710cfbc70 | ||
|
|
3052763020 | ||
|
|
8b80a01d0a | ||
|
|
e465056943 | ||
|
|
16c4c67af1 | ||
|
|
c5dce9cab4 | ||
|
|
8fac6cb9a4 | ||
|
|
9805f6d6b2 | ||
|
|
61c8d54527 | ||
|
|
7c1f7447c5 | ||
|
|
1c2dd12f32 | ||
|
|
e312ac6c46 | ||
|
|
fffcb7f5b4 | ||
|
|
0ec5b31c40 | ||
|
|
93aaa9e509 | ||
|
|
8182a233d1 | ||
|
|
853cc753b8 | ||
|
|
cdfb3b75cb | ||
|
|
89e68489ea | ||
|
|
eec5f0fa26 | ||
|
|
7e8eba3244 | ||
|
|
b987783c62 | ||
|
|
b8bdfa1b9a | ||
|
|
6347d7c0e2 | ||
|
|
27f04e9ab9 | ||
|
|
ab4a66131b | ||
|
|
60d2b852ae | ||
|
|
ec74158ebc | ||
|
|
f6ad5d5c45 | ||
|
|
dd4394a0d6 | ||
|
|
eae55a8595 | ||
|
|
541811a7a6 | ||
|
|
0a63784ca2 | ||
|
|
be2aee957c | ||
|
|
9a4a862808 | ||
|
|
609b55a6d7 | ||
|
|
d1f756c9ab | ||
|
|
58f790c1c6 | ||
|
|
956d046cf0 | ||
|
|
bf7457db59 | ||
|
|
0c866a6f98 | ||
|
|
8179a7e802 | ||
|
|
f501c76acc | ||
|
|
04c6b3331b | ||
|
|
0ea10c77c7 | ||
|
|
782157e1df | ||
|
|
1e3f3555a4 | ||
|
|
8dc4a890ed | ||
|
|
619b8c19ad | ||
|
|
e1e3342b3d | ||
|
|
0651b9a912 | ||
|
|
6491742c3a | ||
|
|
339bb84765 | ||
|
|
b9582f864e | ||
|
|
e2c6dcd6e6 | ||
|
|
fdac4d16f0 | ||
|
|
67085e9dbb | ||
|
|
dec89b5378 | ||
|
|
c7aaa69b4b | ||
|
|
662f2cef9c | ||
|
|
ca523b1f20 | ||
|
|
c28602f275 | ||
|
|
dd11c02b6a | ||
|
|
f890fad90c | ||
|
|
cd7538861a | ||
|
|
013b521838 | ||
|
|
9526009f74 | ||
|
|
2f7713889a | ||
|
|
60cc61cdaa | ||
|
|
bc6827b2e1 | ||
|
|
9dbf7d6bf5 | ||
|
|
46dd73de70 | ||
|
|
9a6136761d | ||
|
|
eaa3e9f612 | ||
|
|
c09daa8513 | ||
|
|
e5a60a8c84 | ||
|
|
79467318f4 | ||
|
|
cc50fc980c | ||
|
|
b8202d89d9 | ||
|
|
f1f42052fb | ||
|
|
3f4e6cf367 | ||
|
|
1aa17bd84d | ||
|
|
c4ae5cfebb | ||
|
|
b0c36c6633 | ||
|
|
461c0ae56c | ||
|
|
42db13d044 | ||
|
|
82cdc870a6 | ||
|
|
0175445ece | ||
|
|
f728ea743f | ||
|
|
230e563ab7 | ||
|
|
26dcbd9ec1 | ||
|
|
a21a5bca1e | ||
|
|
05ec8cba8c | ||
|
|
e57a1a7891 | ||
|
|
11d9b4f21a | ||
|
|
93e4d5d956 | ||
|
|
390af8f476 | ||
|
|
49e88dd74a | ||
|
|
4be2dd3b2a | ||
|
|
2c0b5b733b | ||
|
|
c56ef5c40c | ||
|
|
e1080e1225 | ||
|
|
59be3008fd | ||
|
|
d728c89ba6 | ||
|
|
863aadd9ea | ||
|
|
5d79d4b9dc | ||
|
|
f6f188565b | ||
|
|
29368e8242 | ||
|
|
cd825c5e51 | ||
|
|
874b567974 | ||
|
|
8ee461ae8a | ||
|
|
1388183bf1 | ||
|
|
f1962ccf86 | ||
|
|
0431a072ae | ||
|
|
6781bfd7d8 | ||
|
|
aa56f174db | ||
|
|
3c7e387137 | ||
|
|
2b93fdf357 | ||
|
|
7b97f1533e | ||
|
|
7c246784dc | ||
|
|
0dd3b40694 | ||
|
|
a2a05548f9 | ||
|
|
d4725423a9 | ||
|
|
e9e56689cf | ||
|
|
31bd50c011 | ||
|
|
3b1153cd91 | ||
|
|
6590230bcd | ||
|
|
4f06cd3c2e | ||
|
|
dea9bf9b90 | ||
|
|
726e88ea03 | ||
|
|
70c99c6d44 | ||
|
|
62a8967db1 | ||
|
|
3fc73c21d2 | ||
|
|
d55ae3f8bb | ||
|
|
c8d4b73f94 | ||
|
|
b30dad72ed | ||
|
|
31cdd808ac | ||
|
|
e85a6c09f6 | ||
|
|
025ef6311b | ||
|
|
842ef38868 | ||
|
|
acd23c4c37 | ||
|
|
38d184fe81 | ||
|
|
b0ea7063c7 | ||
|
|
fe819e3512 | ||
|
|
42ca31055a | ||
|
|
652797ba0b | ||
|
|
89c40259f3 | ||
|
|
520fcf195a | ||
|
|
284ce00aef | ||
|
|
db789dc2bf | ||
|
|
6ddc953989 | ||
|
|
1f21e30bb2 | ||
|
|
6c923d3bc6 | ||
|
|
cd3d1333de | ||
|
|
dff53b8144 | ||
|
|
513c943e87 | ||
|
|
3b1cc30e8d | ||
|
|
a4ca44ca14 | ||
|
|
4ce2f9db50 | ||
|
|
78bdb1928a | ||
|
|
b2bdf01152 | ||
|
|
956f6f1eab | ||
|
|
1e1789f6d1 | ||
|
|
70bd831099 | ||
|
|
01a7978387 | ||
|
|
1a2514f417 | ||
|
|
6143d66504 | ||
|
|
1602fca5ed | ||
|
|
ebcf71c30c | ||
|
|
88f3b29515 | ||
|
|
75e4093067 | ||
|
|
619b0ba052 | ||
|
|
548977f579 | ||
|
|
7551af3eb8 | ||
|
|
814399324f | ||
|
|
50e4871d65 | ||
|
|
db0a765b98 | ||
|
|
092a80f849 | ||
|
|
5b161be334 | ||
|
|
62be761ef1 | ||
|
|
651d392b00 | ||
|
|
6f3977de9d | ||
|
|
91924ec685 | ||
|
|
683a2c5b23 | ||
|
|
1f146f905a | ||
|
|
82f54b5556 | ||
|
|
2b744b2eef | ||
|
|
438b58193d | ||
|
|
2958461970 | ||
|
|
f2d2144932 | ||
|
|
c9cf13a01f | ||
|
|
0cd086cf9c | ||
|
|
b098435290 | ||
|
|
74b007ff66 | ||
|
|
ed96b597c7 | ||
|
|
6b9fc7aa59 | ||
|
|
d438bfbc99 | ||
|
|
7f99d78462 | ||
|
|
a7ff638f4c | ||
|
|
287d5094ec | ||
|
|
f2b4d667d1 | ||
|
|
fff27a4270 | ||
|
|
f90f173826 | ||
|
|
cc88d2a334 | ||
|
|
a0ddf4a945 | ||
|
|
05277a5579 | ||
|
|
c155c6e629 | ||
|
|
a6085c9678 | ||
|
|
86f2bea8c5 | ||
|
|
adf04d29f7 | ||
|
|
9599ffcfb9 | ||
|
|
69d5951296 | ||
|
|
b148359337 | ||
|
|
e31a410096 | ||
|
|
6bf67f44ef | ||
|
|
fdc91ec56c | ||
|
|
884d18bade | ||
|
|
ca2ee26c86 | ||
|
|
59d999956c | ||
|
|
acaad05341 | ||
|
|
65f3464995 | ||
|
|
ee4f725209 | ||
|
|
ef5a04c7ce | ||
|
|
f62e9a2d33 | ||
|
|
da9b4620b3 | ||
|
|
8db0203839 | ||
|
|
92bd3b49c8 | ||
|
|
4110297a8f | ||
|
|
dbcb213691 | ||
|
|
f410d2bd00 | ||
|
|
7a3b5e3571 | ||
|
|
505bc47ae1 | ||
|
|
c9c218fdf0 | ||
|
|
46008a7235 | ||
|
|
2c5b74c960 | ||
|
|
db68fc3a2b | ||
|
|
29490ee665 | ||
|
|
b981055d31 | ||
|
|
edd3547977 | ||
|
|
aa361a70a7 | ||
|
|
7b6bdd8129 | ||
|
|
4aed3385b6 | ||
|
|
2736c3603a | ||
|
|
3ea90467b7 | ||
|
|
683027468e | ||
|
|
269cae3a9f | ||
|
|
9d4a932656 | ||
|
|
1611cf681a | ||
|
|
78474cfae9 | ||
|
|
aaf847040f | ||
|
|
e44540043d | ||
|
|
69be273e01 | ||
|
|
5a1de2f54c | ||
|
|
91eed1ab24 | ||
|
|
3ca8c49334 | ||
|
|
f28b33bbf0 | ||
|
|
537f85205d | ||
|
|
b8a93b6b90 | ||
|
|
764a1ad7e4 | ||
|
|
6a68c6532c | ||
|
|
3b3641568a | ||
|
|
cd686ffdf3 | ||
|
|
ca2cc40769 | ||
|
|
2383a88612 | ||
|
|
48518e9513 | ||
|
|
de79f15068 | ||
|
|
2388e25235 | ||
|
|
c570f08b2b | ||
|
|
2aeb464b43 | ||
|
|
5a3f83f90f | ||
|
|
cb60a44f8a | ||
|
|
b5b8cab717 | ||
|
|
c84329d7a4 | ||
|
|
f988879b6e | ||
|
|
84edfcb541 | ||
|
|
722b5dcc1b | ||
|
|
8396937503 | ||
|
|
2c4dc2951d | ||
|
|
7a58086040 | ||
|
|
19281313dd | ||
|
|
71f7ea686d | ||
|
|
d5d957f6ee | ||
|
|
e371c34237 | ||
|
|
b5be763631 | ||
|
|
f03e5f4fef | ||
|
|
a042f74a88 | ||
|
|
aae586b4ef | ||
|
|
1c3545e234 | ||
|
|
c494f65b84 | ||
|
|
6c47c3327a | ||
|
|
3f698d24e5 | ||
|
|
2ba5d51120 | ||
|
|
c3060e3474 | ||
|
|
59256264ec | ||
|
|
3aa14accd7 | ||
|
|
f93cdcb9c5 | ||
|
|
1b6b4106db | ||
|
|
f25d2870ce | ||
|
|
7921a58988 | ||
|
|
7d30bfc22c | ||
|
|
bdb199c53a | ||
|
|
1e17418585 | ||
|
|
d0ced1fd74 | ||
|
|
303b1f07d3 | ||
|
|
e0db59fd09 | ||
|
|
867853016f | ||
|
|
be6c335bb8 | ||
|
|
b3a1dcd634 | ||
|
|
dec43289f6 | ||
|
|
7ec0304472 | ||
|
|
a22507f835 | ||
|
|
d3ade82f3f | ||
|
|
c43e019d3a | ||
|
|
d68bebeb49 | ||
|
|
c51d7c08b9 | ||
|
|
ddb23bd2ed | ||
|
|
9e05d175a7 | ||
|
|
a34dae549b | ||
|
|
79ca1d7fb0 | ||
|
|
bc6da55e96 | ||
|
|
1bd346cbeb | ||
|
|
d1d954bb3b | ||
|
|
43244b6599 | ||
|
|
c6f1d29538 | ||
|
|
a2cafb251a | ||
|
|
e0b62a46bb | ||
|
|
4aa66b9667 | ||
|
|
11797db866 | ||
|
|
c2c966b761 | ||
|
|
4bf715758f | ||
|
|
946419fc18 | ||
|
|
2b9a869633 | ||
|
|
19cecc3235 | ||
|
|
6e41c10584 | ||
|
|
266d64f7d1 | ||
|
|
725b35196f | ||
|
|
03bbc54023 | ||
|
|
ad8610fa03 | ||
|
|
e86488615a | ||
|
|
ee0e2402b1 | ||
|
|
ddf5e566b0 | ||
|
|
bb08e7635b | ||
|
|
3bde085c57 | ||
|
|
0d6bf9db3e | ||
|
|
f700246bfa | ||
|
|
fca183b203 | ||
|
|
1b2a116518 | ||
|
|
9851035e40 | ||
|
|
aa5ff162b4 | ||
|
|
933697f045 | ||
|
|
91c8f747f4 | ||
|
|
4e98c1bbdb | ||
|
|
0a31f45812 | ||
|
|
e65817ad5b | ||
|
|
43ba6ba686 | ||
|
|
09467d3e24 | ||
|
|
95b9782549 | ||
|
|
454b792afb | ||
|
|
cb4085bfd9 | ||
|
|
9b0dc92025 | ||
|
|
7859a7b5c2 | ||
|
|
bdcf468e52 | ||
|
|
efaca05999 | ||
|
|
316e6171d4 | ||
|
|
04544b3d3c | ||
|
|
85102b0118 | ||
|
|
55de160551 | ||
|
|
cec9f3c4d7 | ||
|
|
16f562e81c | ||
|
|
92ccc0ec84 | ||
|
|
74175f2518 | ||
|
|
0a1ee9e37c | ||
|
|
05f5bac405 | ||
|
|
0195894a50 | ||
|
|
27c1d2144a | ||
|
|
88aba645b8 | ||
|
|
402c213183 | ||
|
|
17acc7caa6 | ||
|
|
6b7a230ca5 | ||
|
|
c7a8c429ed | ||
|
|
f0a1555aca | ||
|
|
ccd338fa50 | ||
|
|
4e7214c6b5 | ||
|
|
2297ee4b81 | ||
|
|
85daec4748 | ||
|
|
cf014656af | ||
|
|
b3b3c2303f | ||
|
|
0ff66c718b | ||
|
|
1bb8a43e04 | ||
|
|
655bbce42a | ||
|
|
9258745ec7 | ||
|
|
fcffab9a4c | ||
|
|
92f7f12bab | ||
|
|
7c40185676 | ||
|
|
abf19f649d | ||
|
|
0a2a716796 | ||
|
|
a27e398923 | ||
|
|
b19af2e135 | ||
|
|
883007aa1b | ||
|
|
ba1470ea9d | ||
|
|
0d285ce993 | ||
|
|
e43bd59688 | ||
|
|
0c8d885c26 | ||
|
|
23fb84029b | ||
|
|
d2dfe3634a | ||
|
|
5489f68e2f | ||
|
|
4eb500cc41 | ||
|
|
871a9fb0c6 | ||
|
|
4cb0152ea1 | ||
|
|
42af8acd1e | ||
|
|
df014dadc3 | ||
|
|
bb657e7432 | ||
|
|
d580695faa | ||
|
|
ba80b691e1 | ||
|
|
41c017c9da | ||
|
|
5078cdbc90 | ||
|
|
e5718351ba | ||
|
|
36ff0d52da | ||
|
|
ad3086b8f1 | ||
|
|
6c29f347b4 | ||
|
|
a86949d0be | ||
|
|
44f03af4b9 | ||
|
|
ce5cfde11e | ||
|
|
40d70bf1fc | ||
|
|
19100d68ef | ||
|
|
1d44aa945d | ||
|
|
68547f767d | ||
|
|
44e5e9d8c9 | ||
|
|
43694777d5 | ||
|
|
f579b1cb9f | ||
|
|
2d32e0fa7d | ||
|
|
6f221678df | ||
|
|
e24040b0a9 | ||
|
|
1d269d2f6d | ||
|
|
d4a7f0b3e1 | ||
|
|
d22099ac33 | ||
|
|
e2cf9f6b74 | ||
|
|
74daa1da64 | ||
|
|
475da05185 | ||
|
|
86bfd4f5e4 | ||
|
|
d453bf3403 | ||
|
|
2b9b034bd2 | ||
|
|
d20cab10b9 | ||
|
|
399737e7c6 | ||
|
|
1c5a2b8892 | ||
|
|
c17e7bec49 | ||
|
|
c71ffdcd1e | ||
|
|
c590c8ff41 | ||
|
|
c0617ceda4 | ||
|
|
f7751d13fe | ||
|
|
3077034b2d | ||
|
|
89cef2ea6c | ||
|
|
211f4b23d1 | ||
|
|
132ec0d2ad | ||
|
|
ae505d8009 | ||
|
|
c356710f1f | ||
|
|
b9d186e8a3 | ||
|
|
03a8160a91 | ||
|
|
f379eee7a3 | ||
|
|
4f5312807b | ||
|
|
cec3c2133a | ||
|
|
05afae60c2 | ||
|
|
6239a567a8 | ||
|
|
e4d418a076 | ||
|
|
c4727d57c8 | ||
|
|
b4a39ba3c4 | ||
|
|
cf627a82cb | ||
|
|
42d31a7085 | ||
|
|
02b8ed7e0b | ||
|
|
5e9087263d | ||
|
|
a20aee5f18 | ||
|
|
1d18908055 | ||
|
|
1c5da35527 | ||
|
|
61c21d2977 | ||
|
|
90e6298e29 | ||
|
|
04b8f0b455 | ||
|
|
8657b0e3e7 | ||
|
|
0089540b07 | ||
|
|
26c47d564f | ||
|
|
62c6d53a21 | ||
|
|
96474b3d99 | ||
|
|
964d16110e | ||
|
|
d57637ee56 | ||
|
|
8ce4bb6dc1 | ||
|
|
a7c88b599c | ||
|
|
e0901f4fe5 | ||
|
|
cf25c308cd | ||
|
|
9d11be899c | ||
|
|
66ddcf98d3 | ||
|
|
3fb683f64e | ||
|
|
52409f86e8 | ||
|
|
91c8a3ebed | ||
|
|
52cb0bbc07 | ||
|
|
eb2a68fec0 | ||
|
|
e05a46b7f5 | ||
|
|
46304c8137 | ||
|
|
7a812ac5ed | ||
|
|
71d6281e39 | ||
|
|
bb30569e41 | ||
|
|
942c55cf51 | ||
|
|
50085a505b | ||
|
|
69964fc788 | ||
|
|
5b96d014b4 | ||
|
|
84c3c3aa9c | ||
|
|
592563124b | ||
|
|
c4d7e5c124 | ||
|
|
34fd0ea2e2 | ||
|
|
5678fc6196 | ||
|
|
4a17e1e736 | ||
|
|
724acdca1d | ||
|
|
a027f1ae2c | ||
|
|
d2891554a4 | ||
|
|
25b4d82d87 | ||
|
|
01ddc7ac36 | ||
|
|
58bbffded4 | ||
|
|
a464c81711 | ||
|
|
f18cbcd9a6 | ||
|
|
ac7d65c4a8 | ||
|
|
65fa47cbcd | ||
|
|
a06b38d2cd | ||
|
|
7773fb8afe | ||
|
|
f4badb3961 | ||
|
|
1e32530d7b | ||
|
|
0ab8e14e4a | ||
|
|
f1109afa79 | ||
|
|
22be97eeda | ||
|
|
d23f3c9428 | ||
|
|
c3e037b24e | ||
|
|
62afb34877 | ||
|
|
fd5ed2e5da | ||
|
|
ca82609d1a | ||
|
|
a6dc5b912f | ||
|
|
d73093a694 | ||
|
|
85560299e0 | ||
|
|
b42b1c1110 | ||
|
|
84733405d0 | ||
|
|
48c8fabb5c | ||
|
|
1f51159d22 | ||
|
|
5f679059d5 | ||
|
|
8afbb4eb4f | ||
|
|
c9b4598fa0 | ||
|
|
ef33846d7d | ||
|
|
8b4ed86071 | ||
|
|
8b74dd824b | ||
|
|
727035a2dc | ||
|
|
fc82fde585 | ||
|
|
f352db8072 | ||
|
|
f075d95183 | ||
|
|
22a66c1192 | ||
|
|
474266f918 | ||
|
|
8e8af51955 | ||
|
|
d5759c9951 | ||
|
|
bf99348faf | ||
|
|
2789af79f6 | ||
|
|
71cae75758 | ||
|
|
90db3ad51b | ||
|
|
32c9aa5087 | ||
|
|
bbaa820278 | ||
|
|
287a5d225a | ||
|
|
f17f7c0c6a | ||
|
|
54c5bcc9a1 | ||
|
|
58ab57201f | ||
|
|
f4829178b3 | ||
|
|
295f013580 | ||
|
|
53b58f65b2 | ||
|
|
cc3875f048 | ||
|
|
d8e1521457 | ||
|
|
e150111b27 | ||
|
|
9e27c28b39 | ||
|
|
5d23068690 | ||
|
|
1050f39789 | ||
|
|
00978c15f7 | ||
|
|
8ea1bd3dfb | ||
|
|
91085e68f9 | ||
|
|
f99f7be836 | ||
|
|
890d9c3216 | ||
|
|
cb0ee07b51 | ||
|
|
8e1d70562d | ||
|
|
5d8d7246c2 | ||
|
|
3b4f521596 | ||
|
|
e5c8cbb3a4 | ||
|
|
7696f4256d | ||
|
|
aaa4861373 | ||
|
|
b3d0b28bd0 | ||
|
|
5c283d941c | ||
|
|
94e90a5d26 | ||
|
|
be6f9f83ce | ||
|
|
3b8edb84a5 | ||
|
|
8fb35c6569 | ||
|
|
3899292e89 | ||
|
|
b06de69f6a | ||
|
|
ac4bc02817 | ||
|
|
708164b878 | ||
|
|
e30cf6e51a | ||
|
|
ee30b78117 | ||
|
|
64aba7e703 | ||
|
|
c6c3a80a86 | ||
|
|
a7af63ca3a | ||
|
|
ae72d30cec | ||
|
|
fec59eb1bf | ||
|
|
f6273b0604 | ||
|
|
deb699a84a | ||
|
|
cac45fd999 | ||
|
|
0bdd1bc68f | ||
|
|
4605846499 | ||
|
|
4670890a82 | ||
|
|
d204b46c18 | ||
|
|
b3208f0ca6 | ||
|
|
be7bf9c193 | ||
|
|
2bba39d723 | ||
|
|
dc567d0d1f | ||
|
|
143837c136 | ||
|
|
11eb18d348 | ||
|
|
d5cf5b91d6 | ||
|
|
48b58e2fad | ||
|
|
7ef3d42e01 | ||
|
|
33461ddc14 | ||
|
|
238c9e6743 | ||
|
|
25cda4f3e6 | ||
|
|
c71703e4db | ||
|
|
5527566a36 | ||
|
|
b6580b303a | ||
|
|
405a27ba90 | ||
|
|
43f66032a9 | ||
|
|
91a1fec5cf | ||
|
|
d0b37a7c90 | ||
|
|
5029495fdb | ||
|
|
ac6ff1a03c | ||
|
|
95ee9f0b00 | ||
|
|
9fba8d2203 | ||
|
|
428f389c7d | ||
|
|
71d4e05fb6 | ||
|
|
1904f8ddc3 | ||
|
|
6b801056b5 | ||
|
|
674cd4a88c | ||
|
|
7217cf4892 | ||
|
|
40c931bdc5 | ||
|
|
2823d4d1e3 | ||
|
|
6d83ecb420 | ||
|
|
c27eb17f23 | ||
|
|
58363bca2c | ||
|
|
3bf39797bb | ||
|
|
3a840cee76 | ||
|
|
04e9897d51 | ||
|
|
ff9095f9c4 | ||
|
|
2e18c88e33 | ||
|
|
9f91c6c884 | ||
|
|
59fd1997f4 | ||
|
|
74f3ce5dcd | ||
|
|
d403c8b44b | ||
|
|
e130da6daa | ||
|
|
2f7dbed321 | ||
|
|
709c10227f | ||
|
|
f0d120a6ca | ||
|
|
1f5480cd5c | ||
|
|
f1e63c55d4 | ||
|
|
562951b77a | ||
|
|
dbd2cb4563 | ||
|
|
1c746feafe | ||
|
|
49683975ab | ||
|
|
4be26fc1a6 | ||
|
|
f590a3a88b | ||
|
|
e26486bd41 | ||
|
|
5c2e890ecd | ||
|
|
715cf7748a | ||
|
|
e0456b4485 | ||
|
|
629bf61655 | ||
|
|
738e6aa3cc | ||
|
|
76dc39ac2d | ||
|
|
43fee6bb94 | ||
|
|
8026729c43 | ||
|
|
d19d63ad7d | ||
|
|
a6141e911c | ||
|
|
5ff2be973c | ||
|
|
73201ba575 | ||
|
|
125d891cd5 | ||
|
|
682a47f739 | ||
|
|
594e47efdf | ||
|
|
f09a45382e | ||
|
|
d78b845575 | ||
|
|
1929b47dda | ||
|
|
3efa7bdcc2 | ||
|
|
6a9eb87c35 | ||
|
|
3eba3e07c6 | ||
|
|
9f06869f76 | ||
|
|
3f41261580 | ||
|
|
551249fb69 | ||
|
|
1a610022cf | ||
|
|
524ff21b7f | ||
|
|
913c140be8 |
@@ -1,12 +1,23 @@
|
||||
./.*
|
||||
./*.md
|
||||
./*.yaml
|
||||
./apis
|
||||
./deploy
|
||||
./Dockerfile
|
||||
./generated/1.1*
|
||||
./internal/mocks
|
||||
./LICENSE
|
||||
./site/
|
||||
./test
|
||||
**/*_test.go
|
||||
# This is effectively a copy of the .gitignore file.
|
||||
# The whole git repo, including the .git directory, should get copied into the Docker build context,
|
||||
# to enable the use of hack/get-ldflags.sh inside the Dockerfile.
|
||||
# When you change the .gitignore file, please consider also changing this file.
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# GoLand
|
||||
.idea
|
||||
|
||||
# MacOS Desktop Services Store
|
||||
.DS_Store
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,2 +1,3 @@
|
||||
*.go.tmpl linguist-language=Go
|
||||
hack/Dockerfile_fips linguist-language=Dockerfile
|
||||
generated/** linguist-generated
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Feature proposal
|
||||
name: Feature request
|
||||
about: Suggest a way to improve this project
|
||||
title: ''
|
||||
labels: ''
|
||||
@@ -16,12 +16,15 @@ It is recommended that you include screenshots and logs to help everyone achieve
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Are you considering submitting a PR for this feature?**
|
||||
@@ -32,4 +35,5 @@ A clear and concise description of any alternative solutions or features you've
|
||||
- **How will this feature be documented?**
|
||||
|
||||
**Additional context**
|
||||
|
||||
Add any other context or screenshots about the feature request here.
|
||||
34
.github/ISSUE_TEMPLATE/proposal_tracking.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/proposal_tracking.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Proposal tracking
|
||||
about: A tracking issue for a proposal document
|
||||
title: '[Proposal] Your proposal title'
|
||||
labels: 'proposal-tracking'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Hey! Thanks for opening an issue!
|
||||
|
||||
This type of issue should only be opened if you intend to create a
|
||||
formal proposal document. Please refer to the proposal process in
|
||||
[proposals/README.md](proposals/README.md).
|
||||
|
||||
Please title this issue starting with `[Proposal]` followed by a
|
||||
title for what you are going to propose. For example:
|
||||
`[Proposal] Lunar landing module authentication via Pinniped`.
|
||||
|
||||
-->
|
||||
|
||||
### Proposal Tracking Issue
|
||||
|
||||
- Proposal: <!-- this starts empty, then please update to link to proposal PR, then also link to proposal doc file after it is merged -->
|
||||
|
||||
- Discussion Links: <!-- link to any mailing list threads, Slack conversations, community meetings, or other places where the proposal was discussed, if any -->
|
||||
- <!-- A -->
|
||||
- <!-- B -->
|
||||
|
||||
- Pull requests: <!-- link to all PRs related to this proposal such as updates to the proposal doc, implementation PRs, etc. - keep this list up to date -->
|
||||
- <!-- #123: briefly describe this PR -->
|
||||
- <!-- #456: briefly describe this PR -->
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -3,6 +3,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
open-pull-requests-limit: 100
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -11,3 +12,8 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/hack" # this should keep the FIPS dockerfile updated per https://github.com/dependabot/feedback/issues/145#issuecomment-414738498
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
57
.github/workflows/codeql-analysis.yml
vendored
Normal file
57
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, release* ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main, release* ]
|
||||
schedule:
|
||||
- cron: '39 13 * * 2'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go', 'javascript' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
55
.github/workflows/scorecards.yml
vendored
Normal file
55
.github/workflows/scorecards.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Scorecards supply-chain security
|
||||
on:
|
||||
# Only the default branch is supported.
|
||||
branch_protection_rule:
|
||||
schedule:
|
||||
- cron: '29 11 * * 3'
|
||||
push:
|
||||
branches: [ main, release* ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecards analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # v3.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@c1aec4ac820532bab364f02a81873c555a0ba3a1 # v1.0.4
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# Read-only PAT token. To create it,
|
||||
# follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.
|
||||
repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
|
||||
# Publish the results to enable scorecard badges. For more details, see
|
||||
# https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories, `publish_results` will automatically be set to `false`,
|
||||
# regardless of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional).
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # v1.0.26
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
# When you change this file, please consider also changing the .dockerignore file.
|
||||
# See comments at the top of .dockerignore for more information.
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
@@ -11,9 +14,6 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# GoLand
|
||||
.idea
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ linters:
|
||||
- godot
|
||||
- goheader
|
||||
- goimports
|
||||
- golint
|
||||
- revive
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- misspell
|
||||
@@ -44,10 +44,9 @@ linters:
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- rowserrcheck
|
||||
- scopelint
|
||||
- exportloopref
|
||||
- sqlclosecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
|
||||
@@ -11,6 +11,7 @@ repos:
|
||||
- id: check-json
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
exclude: 'securetls*' # prevent the linter from running in this file because it's not smart enough not to trim the nmap test output.
|
||||
- id: check-merge-conflict
|
||||
- id: check-added-large-files
|
||||
- id: check-byte-order-marker
|
||||
|
||||
@@ -10,15 +10,28 @@ Please see the [Code of Conduct](./CODE_OF_CONDUCT.md).
|
||||
|
||||
See [SCOPE.md](./SCOPE.md) for some guidelines about what we consider in and out of scope for Pinniped.
|
||||
|
||||
## Roadmap
|
||||
|
||||
The near-term and mid-term roadmap for the work planned for the project [maintainers](MAINTAINERS.md) is documented in [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
## Community Meetings
|
||||
|
||||
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM ET. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
|
||||
Pinniped is better because of our contributors and [maintainers](MAINTAINERS.md). It is because of you that we can bring great
|
||||
software to the community. Please join us during our online community meetings,
|
||||
occurring every first and third Thursday of the month at 9 AM PT / 12 PM ET.
|
||||
Use [this Zoom Link](https://go.pinniped.dev/community/zoom)
|
||||
to attend and add any agenda items you wish to discuss
|
||||
to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view).
|
||||
Join our [Google Group](https://groups.google.com/g/project-pinniped) to receive invites to this meeting.
|
||||
|
||||
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
|
||||
|
||||
## Discussion
|
||||
|
||||
Got a question, comment, or idea? Please don't hesitate to reach out via the GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions) tab at the top of this page or reach out in Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
|
||||
Got a question, comment, or idea? Please don't hesitate to reach out
|
||||
via GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions),
|
||||
GitHub [Issues](https://github.com/vmware-tanzu/pinniped/issues),
|
||||
or in the Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
|
||||
|
||||
## Issues
|
||||
|
||||
@@ -45,7 +58,7 @@ guidelines in the issue and pull request templates.
|
||||
To suggest a feature, please first open an
|
||||
[issue](https://github.com/vmware-tanzu/pinniped/issues/new?template=feature-proposal.md)
|
||||
and tag it with `proposal`, or create a new [Discussion](https://github.com/vmware-tanzu/pinniped/discussions).
|
||||
The project team will work with you on your feature request.
|
||||
The project [maintainers](MAINTAINERS.md) will work with you on your feature request.
|
||||
|
||||
Once the feature request has been validated, a [pull request](https://github.com/vmware-tanzu/pinniped/compare)
|
||||
can be opened to implement the feature.
|
||||
@@ -53,25 +66,44 @@ can be opened to implement the feature.
|
||||
For specifics on what to include in your feature request, please follow the
|
||||
guidelines in the issue and pull request templates.
|
||||
|
||||
### Reporting security vulnerabilities
|
||||
|
||||
Please follow the procedure described in [SECURITY.md](SECURITY.md).
|
||||
|
||||
## CLA
|
||||
|
||||
We welcome contributions from everyone but we can only accept them if you sign
|
||||
We welcome contributions from everyone, but we can only accept them if you sign
|
||||
our Contributor License Agreement (CLA). If you would like to contribute and you
|
||||
have not signed it, our CLA-bot will walk you through the process when you open
|
||||
a Pull Request. For questions about the CLA process, see the
|
||||
[FAQ](https://cla.vmware.com/faq) or submit a question through the GitHub issue
|
||||
tracker.
|
||||
|
||||
## Learning about Pinniped
|
||||
|
||||
New to Pinniped?
|
||||
- Start here to learn how to install and use Pinniped: [Learn to use Pinniped for federated authentication to Kubernetes clusters](https://pinniped.dev/docs/tutorials/concierge-and-supervisor-demo/)
|
||||
- Start here to learn how to navigate the source code: [Code Walk-through](https://pinniped.dev/docs/reference/code-walkthrough/)
|
||||
- Other more detailed documentation can be found at: [Pinniped Docs](https://pinniped.dev/docs/)
|
||||
|
||||
## Building
|
||||
|
||||
The [Dockerfile](Dockerfile) at the root of the repo can be used to build and
|
||||
package the code. After making a change to the code, rebuild the docker image with the following command.
|
||||
package the server-side code. After making a change to the code, rebuild the
|
||||
docker image with the following command.
|
||||
|
||||
```bash
|
||||
# From the root directory of the repo...
|
||||
docker build .
|
||||
```
|
||||
|
||||
The Pinniped CLI client can be built for local use with the following command.
|
||||
|
||||
```bash
|
||||
# From the root directory of the repo...
|
||||
go build -o pinniped ./cmd/pinniped
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Running Lint
|
||||
@@ -97,14 +129,15 @@ docker build .
|
||||
- [`kind`](https://kind.sigs.k8s.io/docs/user/quick-start)
|
||||
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
|
||||
- [`ytt`](https://carvel.dev/#getting-started)
|
||||
- [`nmap`](https://nmap.org/download.html)
|
||||
|
||||
On macOS, these tools can be installed with [Homebrew](https://brew.sh/) (assuming you have Chrome installed already):
|
||||
|
||||
```bash
|
||||
brew install kind k14s/tap/ytt k14s/tap/kapp kubectl chromedriver && brew cask install docker
|
||||
brew install kind vmware-tanzu/carvel/ytt vmware-tanzu/carvel/kapp kubectl chromedriver nmap && brew cask install docker
|
||||
```
|
||||
|
||||
1. Create a kind cluster, compile, create container images, and install Pinniped and supporting dependencies using:
|
||||
1. Create a kind cluster, compile, create container images, and install Pinniped and supporting test dependencies using:
|
||||
|
||||
```bash
|
||||
./hack/prepare-for-integration-tests.sh
|
||||
@@ -113,9 +146,14 @@ docker build .
|
||||
1. Run the Pinniped integration tests:
|
||||
|
||||
```bash
|
||||
source /tmp/integration-test-env && go test -v -count 1 -timeout 0 ./test/integration
|
||||
ulimit -n 512 && source /tmp/integration-test-env && go test -v -count 1 -timeout 0 ./test/integration
|
||||
```
|
||||
|
||||
To run specific integration tests, add the `-run` flag to the above command to specify a regexp for the test names.
|
||||
Use a leading `/` on the regexp because the Pinniped integration tests are automatically nested under several parent tests
|
||||
(see [integration/main_test.go](https://github.com/vmware-tanzu/pinniped/blob/main/test/integration/main_test.go)).
|
||||
For example, to run an integration test called `TestE2E`, add `-run /TestE2E` to the command shown above.
|
||||
|
||||
1. After making production code changes, recompile, redeploy, and run tests again by repeating the same
|
||||
commands described above. If there are only test code changes, then simply run the tests again.
|
||||
|
||||
|
||||
44
Dockerfile
44
Dockerfile
@@ -1,9 +1,9 @@
|
||||
# syntax = docker/dockerfile:1.0-experimental
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
# Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
FROM golang:1.16.5 as build-env
|
||||
FROM golang:1.18.1 as build-env
|
||||
|
||||
WORKDIR /work
|
||||
COPY . .
|
||||
@@ -16,32 +16,26 @@ RUN \
|
||||
--mount=type=cache,target=/cache/gocache \
|
||||
--mount=type=cache,target=/cache/gomodcache \
|
||||
mkdir out && \
|
||||
GOCACHE=/cache/gocache \
|
||||
GOMODCACHE=/cache/gomodcache \
|
||||
CGO_ENABLED=0 \
|
||||
GOOS=linux \
|
||||
GOARCH=amd64 \
|
||||
go build -v -ldflags "$(hack/get-ldflags.sh)" -o out \
|
||||
./cmd/pinniped-concierge/... \
|
||||
./cmd/pinniped-supervisor/... \
|
||||
./cmd/local-user-authenticator/...
|
||||
export GOCACHE=/cache/gocache GOMODCACHE=/cache/gomodcache CGO_ENABLED=0 GOOS=linux GOARCH=amd64 && \
|
||||
go build -v -trimpath -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-concierge-kube-cert-agent ./cmd/pinniped-concierge-kube-cert-agent/... && \
|
||||
go build -v -trimpath -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-server ./cmd/pinniped-server/... && \
|
||||
ln -s /usr/local/bin/pinniped-server /usr/local/bin/pinniped-concierge && \
|
||||
ln -s /usr/local/bin/pinniped-server /usr/local/bin/pinniped-supervisor && \
|
||||
ln -s /usr/local/bin/pinniped-server /usr/local/bin/local-user-authenticator
|
||||
|
||||
# Use a Debian slim image to grab a reasonable default CA bundle.
|
||||
FROM debian:10.9-slim AS get-ca-bundle-env
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* /var/cache/debconf/*
|
||||
# Use a distroless runtime image with CA certificates, timezone data, and not much else.
|
||||
FROM gcr.io/distroless/static:nonroot@sha256:2556293984c5738fc75208cce52cf0a4762c709cf38e4bf8def65a61992da0ad
|
||||
|
||||
# Use a runtime image based on Debian slim.
|
||||
FROM debian:10.9-slim
|
||||
COPY --from=get-ca-bundle-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
# Copy the server binary from the build-env stage.
|
||||
COPY --from=build-env /usr/local/bin /usr/local/bin
|
||||
|
||||
# Copy the binaries from the build-env stage.
|
||||
COPY --from=build-env /work/out/ /usr/local/bin/
|
||||
|
||||
# Document the ports
|
||||
EXPOSE 8080 8443
|
||||
# Document the default server ports for the various server apps
|
||||
EXPOSE 8443 8444 10250
|
||||
|
||||
# Run as non-root for security posture
|
||||
USER 1001:1001
|
||||
# Use the same non-root user as https://github.com/GoogleContainerTools/distroless/blob/fc3c4eaceb0518900f886aae90407c43be0a42d9/base/base.bzl#L9
|
||||
# This is a workaround for https://github.com/GoogleContainerTools/distroless/issues/718
|
||||
USER 65532:65532
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["/usr/local/bin/pinniped-concierge"]
|
||||
ENTRYPOINT ["/usr/local/bin/pinniped-server"]
|
||||
|
||||
68
GOVERNANCE.md
Normal file
68
GOVERNANCE.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Pinniped Governance
|
||||
|
||||
This document defines the project governance for Pinniped.
|
||||
|
||||
# Overview
|
||||
|
||||
**Pinniped** is committed to building an open, inclusive, productive and self-governing open source community focused on
|
||||
building authentication services for Kubernetes clusters. The community is governed by this document which defines how
|
||||
all members should work together to achieve this goal.
|
||||
|
||||
# Code of Conduct
|
||||
|
||||
The Pinniped community abides by this
|
||||
[code of conduct](https://github.com/vmware-tanzu/pinniped/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
# Community Roles
|
||||
|
||||
* **Users:** Members that engage with the Pinniped community via any medium (Slack, GitHub, mailing lists, etc.).
|
||||
* **Contributors:** Do regular contributions to the Pinniped project (documentation, code reviews, responding to issues,
|
||||
participating in proposal discussions, contributing code, etc.).
|
||||
* **Maintainers:** Responsible for the overall health and direction of the project. They are the final reviewers of PRs
|
||||
and responsible for Pinniped releases.
|
||||
|
||||
# Maintainers
|
||||
|
||||
New maintainers must be nominated by an existing maintainer and must be elected by a supermajority of existing
|
||||
maintainers. Likewise, maintainers can be removed by a supermajority of the existing maintainers or can resign by
|
||||
notifying one of the maintainers.
|
||||
|
||||
**Note:** If a maintainer leaves their employer they are still considered a maintainer of Pinniped, unless they
|
||||
voluntarily resign. Employment is not taken into consideration when determining maintainer eligibility unless the
|
||||
company itself violates our [Code of Conduct](https://github.com/vmware-tanzu/pinniped/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
# Decision Making
|
||||
|
||||
Ideally, all project decisions are resolved by consensus. If impossible, any maintainer may call a vote. Unless
|
||||
otherwise specified in this document, any vote will be decided by a supermajority of maintainers.
|
||||
|
||||
## Supermajority
|
||||
|
||||
A supermajority is defined as two-thirds of members in the group. A supermajority of maintainers is required for certain
|
||||
decisions as outlined in this document. A supermajority vote is equivalent to the number of votes in favor being at
|
||||
least twice the number of votes against. A vote to abstain equals not voting at all. For example, if you have 5
|
||||
maintainers who all cast non-abstaining votes, then a supermajority vote is at least 4 votes in favor. Voting on
|
||||
decisions can happen on the mailing list, GitHub, Slack, email, or via a voting service, when appropriate. Maintainers
|
||||
can either vote "agree, yes, +1", "disagree, no, -1", or "abstain". A vote passes when supermajority is met.
|
||||
|
||||
## Lazy Consensus
|
||||
|
||||
To maintain velocity in Pinniped, the concept of [Lazy Consensus](http://en.osswiki.info/concepts/lazy_consensus) is
|
||||
practiced.
|
||||
|
||||
Other maintainers may chime in and request additional time for review, but should remain cognizant of blocking progress
|
||||
and abstain from delaying progress unless absolutely needed. The expectation is that blocking progress is accompanied by
|
||||
a guarantee to review and respond to the relevant action in short order.
|
||||
|
||||
Lazy consensus does not apply to the process of:
|
||||
|
||||
* Removal of maintainers from Pinniped
|
||||
|
||||
## Updating Governance
|
||||
|
||||
All substantive changes in Governance, including substantive changes to the proposal process, require a supermajority
|
||||
agreement by all maintainers.
|
||||
|
||||
# Proposal Process
|
||||
|
||||
The proposal process is defined in [proposals/README.md](proposals/README.md).
|
||||
@@ -4,16 +4,21 @@ This is the current list of maintainers for the Pinniped project.
|
||||
|
||||
| Maintainer | GitHub ID | Affiliation |
|
||||
| --------------- | --------- | ----------- |
|
||||
| Andrew Keesler | [ankeesler](https://github.com/ankeesler) | [VMware](https://www.github.com/vmware/) |
|
||||
| Margo Crawford | [margocrawf](https://github.com/margocrawf) | [VMware](https://www.github.com/vmware/) |
|
||||
| Matt Moyer | [mattmoyer](https://github.com/mattmoyer) | [VMware](https://www.github.com/vmware/) |
|
||||
| Mo Khan | [enj](https://github.com/enj) | [VMware](https://www.github.com/vmware/) |
|
||||
| Pablo Schuhmacher | [pabloschuhmacher](https://github.com/pabloschuhmacher) | [VMware](https://www.github.com/vmware/) |
|
||||
| Anjali Telang | [anjaltelang](https://github.com/anjaltelang) | [VMware](https://www.github.com/vmware/) |
|
||||
| Ryan Richard | [cfryanr](https://github.com/cfryanr) | [VMware](https://www.github.com/vmware/) |
|
||||
|
||||
## Emeritus Maintainers
|
||||
|
||||
* Andrew Keesler, [ankeesler](https://github.com/ankeesler)
|
||||
* Pablo Schuhmacher, [pabloschuhmacher](https://github.com/pabloschuhmacher)
|
||||
* Matt Moyer, [mattmoyer](https://github.com/mattmoyer)
|
||||
|
||||
## Pinniped Contributors & Stakeholders
|
||||
|
||||
| Feature Area | Lead |
|
||||
| ----------------------------- | :---------------------: |
|
||||
| Technical Lead | Matt Moyer (mattmoyer) |
|
||||
| Product Management | Pablo Schuhmacher (pabloschuhmacher) |
|
||||
| Technical Lead | Mo Khan (enj) |
|
||||
| Product Management | Anjali Telang (anjaltelang) |
|
||||
| Community Management | Nigel Brown (pnbrown) |
|
||||
|
||||
78
README.md
78
README.md
@@ -1,63 +1,51 @@
|
||||
<img src="site/content/docs/img/pinniped_logo.svg" alt="Pinniped Logo" width="100%"/>
|
||||
<a href="https://pinniped.dev" target="_blank">
|
||||
<img src="site/content/docs/img/pinniped_logo.svg" alt="Pinniped Logo" width="100%"/>
|
||||
</a>
|
||||
|
||||
## Overview
|
||||
|
||||
Pinniped provides identity services to Kubernetes.
|
||||
|
||||
Pinniped allows cluster administrators to easily plug in external identity
|
||||
providers (IDPs) into Kubernetes clusters. This is achieved via a uniform
|
||||
install procedure across all types and origins of Kubernetes clusters,
|
||||
declarative configuration via Kubernetes APIs, enterprise-grade integrations
|
||||
with IDPs, and distribution-specific integration strategies.
|
||||
- Easily plug in external identity providers into Kubernetes clusters while offering a simple install and configuration experience. Leverage first class integration with Kubernetes and kubectl command-line.
|
||||
- Give users a consistent, unified login experience across all your clusters, including on-premises and managed cloud environments.
|
||||
- Securely integrate with an enterprise IDP using standard protocols or use secure, externally managed identities instead of relying on simple, shared credentials.
|
||||
|
||||
### Example use cases
|
||||
|
||||
* Your team uses a large enterprise IDP, and has many clusters that they
|
||||
manage. Pinniped provides:
|
||||
* Seamless and robust integration with the IDP
|
||||
* Easy installation across clusters of any type and origin
|
||||
* A simplified login flow across all clusters
|
||||
* Your team shares a single cluster. Pinniped provides:
|
||||
* Simple configuration to integrate an IDP
|
||||
* Individual, revocable identities
|
||||
|
||||
### Architecture
|
||||
|
||||
The Pinniped Supervisor component offers identity federation to enable a user to
|
||||
access multiple clusters with a single daily login to their external IDP. The
|
||||
Pinniped Supervisor supports various external [IDP
|
||||
types](https://github.com/vmware-tanzu/pinniped/tree/main/generated/1.20#k8s-api-idp-supervisor-pinniped-dev-v1alpha1).
|
||||
|
||||
The Pinniped Concierge component offers credential exchange to enable a user to
|
||||
exchange an external credential for a short-lived, cluster-specific
|
||||
credential. Pinniped supports various [authentication
|
||||
methods](https://github.com/vmware-tanzu/pinniped/tree/main/generated/1.20#authenticationconciergepinnipeddevv1alpha1)
|
||||
and implements different integration strategies for various Kubernetes
|
||||
distributions to make authentication possible.
|
||||
|
||||
The Pinniped Concierge can be configured to hook into the Pinniped Supervisor's
|
||||
federated credentials, or it can authenticate users directly via external IDP
|
||||
credentials.
|
||||
|
||||
To learn more, see [architecture](https://pinniped.dev/docs/background/architecture/).
|
||||
To learn more, please visit the Pinniped project's website, https://pinniped.dev.
|
||||
|
||||
## Getting started with Pinniped
|
||||
|
||||
Care to kick the tires? It's easy to [install and try Pinniped](https://pinniped.dev/docs/).
|
||||
|
||||
## Community meetings
|
||||
|
||||
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occurring every first and third Thursday of the month at 9 AM PT / 12 PM PT. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/g/project-pinniped) to receive invites to this meeting.
|
||||
|
||||
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
|
||||
|
||||
## Discussion
|
||||
|
||||
Got a question, comment, or idea? Please don't hesitate to reach out via the GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions) tab at the top of this page or reach out in Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
|
||||
Got a question, comment, or idea? Please don't hesitate to reach out
|
||||
via GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions),
|
||||
GitHub [Issues](https://github.com/vmware-tanzu/pinniped/issues),
|
||||
or in the Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome. Before contributing, please see the [contributing guide](CONTRIBUTING.md).
|
||||
Want to get involved? Contributions are welcome.
|
||||
|
||||
Please see the [contributing guide](CONTRIBUTING.md) for more information about reporting bugs, requesting features,
|
||||
building and testing the code, submitting PRs, and other contributor topics.
|
||||
|
||||
## Community meetings
|
||||
|
||||
Pinniped is better because of our contributors and [maintainers](MAINTAINERS.md). It is because of you that we can bring great
|
||||
software to the community. Please join us during our online community meetings, occurring every first and third
|
||||
Thursday of the month at 9 AM PT / 12 PM ET.
|
||||
|
||||
Use [this Zoom Link](https://go.pinniped.dev/community/zoom) to attend and add any agenda items you wish to
|
||||
discuss to [the notes document](https://go.pinniped.dev/community/agenda).
|
||||
Join our [Google Group](https://groups.google.com/g/project-pinniped) to receive invites to this meeting.
|
||||
|
||||
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
|
||||
|
||||
## Adopters
|
||||
|
||||
Some organizations and products using Pinniped are featured in [ADOPTERS.md](ADOPTERS.md).
|
||||
Add your own organization or product [here](https://github.com/vmware-tanzu/pinniped/discussions/152).
|
||||
|
||||
## Reporting security vulnerabilities
|
||||
|
||||
@@ -67,4 +55,4 @@ Please follow the procedure described in [SECURITY.md](SECURITY.md).
|
||||
|
||||
Pinniped is open source and licensed under Apache License Version 2.0. See [LICENSE](LICENSE).
|
||||
|
||||
Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
|
||||
28
ROADMAP.md
28
ROADMAP.md
@@ -33,18 +33,34 @@ The following table includes the current roadmap for Pinniped. If you have any q
|
||||
|
||||
|
||||
|
||||
Last Updated: June 2021
|
||||
Theme|Description|Timeline|
|
||||
Last Updated: March 2022
|
||||
|Theme|Description|Timeline|
|
||||
|--|--|--|
|
||||
|Remote OIDC login support|Add support for logging in from remote hosts without web browsers in the Pinniped CLI and Supervisor|Jun 2021|
|
||||
|AD Support|Extends upstream IDP protocols|Jun 2021|
|
||||
|Wider Concierge cluster support|Support for more cluster types in the Concierge|Jul 2021|
|
||||
|Improving Security Posture|Support Audit logging of security events related to Authentication |May/June 2022|
|
||||
|Improving Usability|Support for integrating with UI/Dashboards |May/June 2022|
|
||||
|Improving Security Posture|TLS hardening contd|June/July 2022|
|
||||
|Multiple IDP support|Support multiple IDPs configured on a single Supervisor|Exploring/Ongoing|
|
||||
|Improving Security Posture|mTLS for Supervisor sessions |Exploring/Ongoing|
|
||||
|Improving Security Posture|Key management/rotation for Pinniped components with minimal downtime |Exploring/Ongoing|
|
||||
|Improving Security Posture|Support for Session Logout |Exploring/Ongoing|
|
||||
|Improving Security Posture|Support for Idle Session/ Inactivity timeout|Exploring/Ongoing|
|
||||
|Improving Security Posture|Support for Max Concurrent Sessions|Exploring/Ongoing|
|
||||
|Improving Security Posture|Support for configurable Session Length |Exploring/Ongoing|
|
||||
|Improving Security Posture|Reject use of username and groups with system: prefix |Exploring/Ongoing|
|
||||
|Improving Security Posture|Support for using external KMS for Supervisor signing keys |Exploring/Ongoing|
|
||||
|Improving Security Posture|Client side use of Secure Enclaves for Session data |Exploring/Ongoing|
|
||||
|Improving Security Posture|Enforce the use of HTTP Strict Transport (HSTS) |Exploring/Ongoing|
|
||||
|Improving Security Posture|Assert that Pinniped runs under the restricted PSP version2 levels |Exploring/Ongoing|
|
||||
|Wider Concierge cluster support|Support for OpenShift cluster types in the Concierge|Exploring/Ongoing|
|
||||
|Identity transforms|Support prefixing, filtering, or performing coarse-grained checks on upstream users and groups|Exploring/Ongoing|
|
||||
|CLI SSO|Support Kerberos based authentication on CLI |Exploring/Ongoing|
|
||||
|Extended IDP support|Support more types of identity providers on the Supervisor|Exploring/Ongoing|
|
||||
|Improved Documentation|Reorganizing and improving Pinniped docs; new how-to guides and tutorials|Exploring/Ongoing|
|
||||
|Improving Security Posture|Offer the best security posture for Kubernetes cluster authentication|Exploring/Ongoing|
|
||||
|Improve our CI/CD systems|Upgrade tests; make Kind more efficient and reliable for CI ; Windows tests; performance tests; scale tests; soak tests|Exploring/Ongoing|
|
||||
|CLI Improvements|Improving CLI UX for setting up Supervisor IDPs|Exploring/Ongoing|
|
||||
|Telemetry|Adding some useful phone home metrics as well as some vanity metrics|Exploring/Ongoing|
|
||||
|Observability|Expose Pinniped metrics through Prometheus Integration|Exploring/Ongoing|
|
||||
|Device Code Flow|Add support for OAuth 2.0 Device Authorization Grant in the Pinniped CLI and Supervisor|Exploring/Ongoing|
|
||||
|Supervisor with New Clients|Enable registering new clients with Supervisor|Exploring/Ongoing|
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -61,6 +61,8 @@ type JWTTokenClaims struct {
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
|
||||
// +kubebuilder:printcolumn:name="Audience",type=string,JSONPath=`.spec.audience`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type JWTAuthenticator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -33,6 +33,7 @@ type WebhookAuthenticatorSpec struct {
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.spec.endpoint`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type WebhookAuthenticator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -96,7 +96,7 @@ type ImpersonationProxySpec struct {
|
||||
// ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will
|
||||
// be served using the external name of the LoadBalancer service or the cluster service DNS name.
|
||||
//
|
||||
// This field must be non-empty when spec.impersonationProxy.service.mode is "None".
|
||||
// This field must be non-empty when spec.impersonationProxy.service.type is "None".
|
||||
//
|
||||
// +optional
|
||||
ExternalEndpoint string `json:"externalEndpoint,omitempty"`
|
||||
@@ -215,6 +215,9 @@ type ImpersonationProxyInfo struct {
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped,scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="ProxyMode",type=string,JSONPath=`.spec.impersonationProxy.mode`
|
||||
// +kubebuilder:printcolumn:name="DefaultStrategy",type=string,JSONPath=`.status.strategies[?(@.status == "Success")].type`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type CredentialIssuer struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validation
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package login
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package login
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package login
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -31,8 +31,9 @@ type FederationDomainTLSSpec struct {
|
||||
// SNI requests do not include port numbers, so all issuers with the same DNS hostname must use the same
|
||||
// SecretName value even if they have different port numbers.
|
||||
//
|
||||
// SecretName is not required when you would like to use only the HTTP endpoints (e.g. when terminating TLS at an
|
||||
// Ingress). It is also not required when you would like all requests to this OIDC Provider's HTTPS endpoints to
|
||||
// SecretName is not required when you would like to use only the HTTP endpoints (e.g. when the HTTP listener is
|
||||
// configured to listen on loopback interfaces or UNIX domain sockets for traffic from a service mesh sidecar).
|
||||
// It is also not required when you would like all requests to this OIDC Provider's HTTPS endpoints to
|
||||
// use the default TLS certificate, which is configured elsewhere.
|
||||
//
|
||||
// When your Issuer URL's host is an IP address, then this field is ignored. SNI does not work for IP addresses.
|
||||
@@ -109,6 +110,9 @@ type FederationDomainStatus struct {
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped
|
||||
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
|
||||
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type FederationDomain struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -34,6 +34,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
&OIDCIdentityProviderList{},
|
||||
&LDAPIdentityProvider{},
|
||||
&LDAPIdentityProviderList{},
|
||||
&ActiveDirectoryIdentityProvider{},
|
||||
&ActiveDirectoryIdentityProviderList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ActiveDirectoryIdentityProviderPhase string
|
||||
|
||||
const (
|
||||
// ActiveDirectoryPhasePending is the default phase for newly-created ActiveDirectoryIdentityProvider resources.
|
||||
ActiveDirectoryPhasePending ActiveDirectoryIdentityProviderPhase = "Pending"
|
||||
|
||||
// ActiveDirectoryPhaseReady is the phase for an ActiveDirectoryIdentityProvider resource in a healthy state.
|
||||
ActiveDirectoryPhaseReady ActiveDirectoryIdentityProviderPhase = "Ready"
|
||||
|
||||
// ActiveDirectoryPhaseError is the phase for an ActiveDirectoryIdentityProvider in an unhealthy state.
|
||||
ActiveDirectoryPhaseError ActiveDirectoryIdentityProviderPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of an Active Directory identity provider.
|
||||
type ActiveDirectoryIdentityProviderStatus struct {
|
||||
// Phase summarizes the overall status of the ActiveDirectoryIdentityProvider.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase ActiveDirectoryIdentityProviderPhase `json:"phase,omitempty"`
|
||||
|
||||
// Represents the observations of an identity provider's current state.
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderBind struct {
|
||||
// SecretName contains the name of a namespace-local Secret object that provides the username and
|
||||
// password for an Active Directory bind user. This account will be used to perform LDAP searches. The Secret should be
|
||||
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
|
||||
// should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
|
||||
// The password must be non-empty.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
SecretName string `json:"secretName"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderUserSearchAttributes struct {
|
||||
// Username specifies the name of the attribute in Active Directory entry whose value shall become the username
|
||||
// of the user after a successful authentication.
|
||||
// Optional, when empty this defaults to "userPrincipalName".
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
|
||||
// UID specifies the name of the attribute in the ActiveDirectory entry which whose value shall be used to uniquely
|
||||
// identify the user within this ActiveDirectory provider after a successful authentication.
|
||||
// Optional, when empty this defaults to "objectGUID".
|
||||
// +optional
|
||||
UID string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderGroupSearchAttributes struct {
|
||||
// GroupName specifies the name of the attribute in the Active Directory entries whose value shall become a group name
|
||||
// in the user's list of groups after a successful authentication.
|
||||
// The value of this field is case-sensitive and must match the case of the attribute name returned by the ActiveDirectory
|
||||
// server in the user's entry. E.g. "cn" for common name. Distinguished names can be used by specifying lower-case "dn".
|
||||
// Optional. When not specified, this defaults to a custom field that looks like "sAMAccountName@domain",
|
||||
// where domain is constructed from the domain components of the group DN.
|
||||
// +optional
|
||||
GroupName string `json:"groupName,omitempty"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderUserSearch struct {
|
||||
// Base is the dn (distinguished name) that should be used as the search base when searching for users.
|
||||
// E.g. "ou=users,dc=example,dc=com".
|
||||
// Optional, when not specified it will be based on the result of a query for the defaultNamingContext
|
||||
// (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
// The default behavior searches your entire domain for users.
|
||||
// It may make sense to specify a subtree as a search base if you wish to exclude some users
|
||||
// or to make searches faster.
|
||||
// +optional
|
||||
Base string `json:"base,omitempty"`
|
||||
|
||||
// Filter is the search filter which should be applied when searching for users. The pattern "{}" must occur
|
||||
// in the filter at least once and will be dynamically replaced by the username for which the search is being run.
|
||||
// E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see
|
||||
// https://ldap.com/ldap-filters.
|
||||
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
|
||||
// Optional. When not specified, the default will be
|
||||
// '(&(objectClass=person)(!(objectClass=computer))(!(showInAdvancedViewOnly=TRUE))(|(sAMAccountName={}")(mail={})(userPrincipalName={})(sAMAccountType=805306368))'
|
||||
// This means that the user is a person, is not a computer, the sAMAccountType is for a normal user account,
|
||||
// and is not shown in advanced view only
|
||||
// (which would likely mean its a system created service account with advanced permissions).
|
||||
// Also, either the sAMAccountName, the userPrincipalName, or the mail attribute matches the input username.
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// Attributes specifies how the user's information should be read from the ActiveDirectory entry which was found as
|
||||
// the result of the user search.
|
||||
// +optional
|
||||
Attributes ActiveDirectoryIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderGroupSearch struct {
|
||||
// Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g.
|
||||
// "ou=groups,dc=example,dc=com".
|
||||
// Optional, when not specified it will be based on the result of a query for the defaultNamingContext
|
||||
// (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
// The default behavior searches your entire domain for groups.
|
||||
// It may make sense to specify a subtree as a search base if you wish to exclude some groups
|
||||
// for security reasons or to make searches faster.
|
||||
// +optional
|
||||
Base string `json:"base,omitempty"`
|
||||
|
||||
// Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user.
|
||||
// The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the
|
||||
// dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or
|
||||
// "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see
|
||||
// https://ldap.com/ldap-filters.
|
||||
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
|
||||
// Optional. When not specified, the default will act as if the filter were specified as
|
||||
// "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})".
|
||||
// This searches nested groups by default.
|
||||
// Note that nested group search can be slow for some Active Directory servers. To disable it,
|
||||
// you can set the filter to
|
||||
// "(&(objectClass=group)(member={})"
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as
|
||||
// the result of the group search.
|
||||
// +optional
|
||||
Attributes ActiveDirectoryIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
|
||||
|
||||
// The user's group membership is refreshed as they interact with the supervisor
|
||||
// to obtain new credentials (as their old credentials expire). This allows group
|
||||
// membership changes to be quickly reflected into Kubernetes clusters. Since
|
||||
// group membership is often used to bind authorization policies, it is important
|
||||
// to keep the groups observed in Kubernetes clusters in-sync with the identity
|
||||
// provider.
|
||||
//
|
||||
// In some environments, frequent group membership queries may result in a
|
||||
// significant performance impact on the identity provider and/or the supervisor.
|
||||
// The best approach to handle performance impacts is to tweak the group query
|
||||
// to be more performant, for example by disabling nested group search or by
|
||||
// using a more targeted group search base.
|
||||
//
|
||||
// If the group search query cannot be made performant and you are willing to
|
||||
// have group memberships remain static for approximately a day, then set
|
||||
// skipGroupRefresh to true. This is an insecure configuration as authorization
|
||||
// policies that are bound to group membership will not notice if a user has
|
||||
// been removed from a particular group until their next login.
|
||||
//
|
||||
// This is an experimental feature that may be removed or significantly altered
|
||||
// in the future. Consumers of this configuration should carefully read all
|
||||
// release notes before upgrading to ensure that the meaning of this field has
|
||||
// not changed.
|
||||
SkipGroupRefresh bool `json:"skipGroupRefresh,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring an ActiveDirectory identity provider.
|
||||
type ActiveDirectoryIdentityProviderSpec struct {
|
||||
// Host is the hostname of this Active Directory identity provider, i.e., where to connect. For example: ldap.example.com:636.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Host string `json:"host"`
|
||||
|
||||
// TLS contains the connection settings for how to establish the connection to the Host.
|
||||
TLS *TLSSpec `json:"tls,omitempty"`
|
||||
|
||||
// Bind contains the configuration for how to provide access credentials during an initial bind to the ActiveDirectory server
|
||||
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
|
||||
Bind ActiveDirectoryIdentityProviderBind `json:"bind,omitempty"`
|
||||
|
||||
// UserSearch contains the configuration for searching for a user by name in Active Directory.
|
||||
UserSearch ActiveDirectoryIdentityProviderUserSearch `json:"userSearch,omitempty"`
|
||||
|
||||
// GroupSearch contains the configuration for searching for a user's group membership in ActiveDirectory.
|
||||
GroupSearch ActiveDirectoryIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
|
||||
}
|
||||
|
||||
// ActiveDirectoryIdentityProvider describes the configuration of an upstream Microsoft Active Directory identity provider.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
|
||||
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
|
||||
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type ActiveDirectoryIdentityProvider struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec for configuring the identity provider.
|
||||
Spec ActiveDirectoryIdentityProviderSpec `json:"spec"`
|
||||
|
||||
// Status of the identity provider.
|
||||
Status ActiveDirectoryIdentityProviderStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// List of ActiveDirectoryIdentityProvider objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type ActiveDirectoryIdentityProviderList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []ActiveDirectoryIdentityProvider `json:"items"`
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -119,6 +119,31 @@ type LDAPIdentityProviderGroupSearch struct {
|
||||
// the result of the group search.
|
||||
// +optional
|
||||
Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
|
||||
|
||||
// The user's group membership is refreshed as they interact with the supervisor
|
||||
// to obtain new credentials (as their old credentials expire). This allows group
|
||||
// membership changes to be quickly reflected into Kubernetes clusters. Since
|
||||
// group membership is often used to bind authorization policies, it is important
|
||||
// to keep the groups observed in Kubernetes clusters in-sync with the identity
|
||||
// provider.
|
||||
//
|
||||
// In some environments, frequent group membership queries may result in a
|
||||
// significant performance impact on the identity provider and/or the supervisor.
|
||||
// The best approach to handle performance impacts is to tweak the group query
|
||||
// to be more performant, for example by disabling nested group search or by
|
||||
// using a more targeted group search base.
|
||||
//
|
||||
// If the group search query cannot be made performant and you are willing to
|
||||
// have group memberships remain static for approximately a day, then set
|
||||
// skipGroupRefresh to true. This is an insecure configuration as authorization
|
||||
// policies that are bound to group membership will not notice if a user has
|
||||
// been removed from a particular group until their next login.
|
||||
//
|
||||
// This is an experimental feature that may be removed or significantly altered
|
||||
// in the future. Consumers of this configuration should carefully read all
|
||||
// release notes before upgrading to ensure that the meaning of this field has
|
||||
// not changed.
|
||||
SkipGroupRefresh bool `json:"skipGroupRefresh,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring an LDAP identity provider.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -20,7 +20,7 @@ const (
|
||||
PhaseError OIDCIdentityProviderPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of an OIDC identity provider.
|
||||
// OIDCIdentityProviderStatus is the status of an OIDC identity provider.
|
||||
type OIDCIdentityProviderStatus struct {
|
||||
// Phase summarizes the overall status of the OIDCIdentityProvider.
|
||||
// +kubebuilder:default=Pending
|
||||
@@ -38,21 +38,104 @@ type OIDCIdentityProviderStatus struct {
|
||||
// OIDCAuthorizationConfig provides information about how to form the OAuth2 authorization
|
||||
// request parameters.
|
||||
type OIDCAuthorizationConfig struct {
|
||||
// AdditionalScopes are the scopes in addition to "openid" that will be requested as part of the authorization
|
||||
// request flow with an OIDC identity provider. By default only the "openid" scope will be requested.
|
||||
// additionalScopes are the additional scopes that will be requested from your OIDC provider in the authorization
|
||||
// request during an OIDC Authorization Code Flow and in the token request during a Resource Owner Password Credentials
|
||||
// Grant. Note that the "openid" scope will always be requested regardless of the value in this setting, since it is
|
||||
// always required according to the OIDC spec. By default, when this field is not set, the Supervisor will request
|
||||
// the following scopes: "openid", "offline_access", "email", and "profile". See
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims for a description of the "profile" and "email"
|
||||
// scopes. See https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess for a description of the
|
||||
// "offline_access" scope. This default value may change in future versions of Pinniped as the standard evolves,
|
||||
// or as common patterns used by providers who implement the standard in the ecosystem evolve.
|
||||
// By setting this list to anything other than an empty list, you are overriding the
|
||||
// default value, so you may wish to include some of "offline_access", "email", and "profile" in your override list.
|
||||
// If you do not want any of these scopes to be requested, you may set this list to contain only "openid".
|
||||
// Some OIDC providers may also require a scope to get access to the user's group membership, in which case you
|
||||
// may wish to include it in this list. Sometimes the scope to request the user's group membership is called
|
||||
// "groups", but unfortunately this is not specified in the OIDC standard.
|
||||
// Generally speaking, you should include any scopes required to cause the appropriate claims to be the returned by
|
||||
// your OIDC provider in the ID token or userinfo endpoint results for those claims which you would like to use in
|
||||
// the oidcClaims settings to determine the usernames and group memberships of your Kubernetes users. See
|
||||
// your OIDC provider's documentation for more information about what scopes are available to request claims.
|
||||
// Additionally, the Pinniped Supervisor requires that your OIDC provider returns refresh tokens to the Supervisor
|
||||
// from these authorization flows. For most OIDC providers, the scope required to receive refresh tokens will be
|
||||
// "offline_access". See the documentation of your OIDC provider's authorization and token endpoints for its
|
||||
// requirements for what to include in the request in order to receive a refresh token in the response, if anything.
|
||||
// Note that it may be safe to send "offline_access" even to providers which do not require it, since the provider
|
||||
// may ignore scopes that it does not understand or require (see
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-3.3). In the unusual case that you must avoid sending the
|
||||
// "offline_access" scope, then you must override the default value of this setting. This is required if your OIDC
|
||||
// provider will reject the request when it includes "offline_access" (e.g. GitLab's OIDC provider).
|
||||
// +optional
|
||||
AdditionalScopes []string `json:"additionalScopes,omitempty"`
|
||||
|
||||
// additionalAuthorizeParameters are extra query parameters that should be included in the authorize request to your
|
||||
// OIDC provider in the authorization request during an OIDC Authorization Code Flow. By default, no extra
|
||||
// parameters are sent. The standard parameters that will be sent are "response_type", "scope", "client_id",
|
||||
// "state", "nonce", "code_challenge", "code_challenge_method", and "redirect_uri". These parameters cannot be
|
||||
// included in this setting. Additionally, the "hd" parameter cannot be included in this setting at this time.
|
||||
// The "hd" parameter is used by Google's OIDC provider to provide a hint as to which "hosted domain" the user
|
||||
// should use during login. However, Pinniped does not yet support validating the hosted domain in the resulting
|
||||
// ID token, so it is not yet safe to use this feature of Google's OIDC provider with Pinniped.
|
||||
// This setting does not influence the parameters sent to the token endpoint in the Resource Owner Password
|
||||
// Credentials Grant. The Pinniped Supervisor requires that your OIDC provider returns refresh tokens to the
|
||||
// Supervisor from the authorization flows. Some OIDC providers may require a certain value for the "prompt"
|
||||
// parameter in order to properly request refresh tokens. See the documentation of your OIDC provider's
|
||||
// authorization endpoint for its requirements for what to include in the request in order to receive a refresh
|
||||
// token in the response, if anything. If your provider requires the prompt parameter to request a refresh token,
|
||||
// then include it here. Also note that most providers also require a certain scope to be requested in order to
|
||||
// receive refresh tokens. See the additionalScopes setting for more information about using scopes to request
|
||||
// refresh tokens.
|
||||
// +optional
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
AdditionalAuthorizeParameters []Parameter `json:"additionalAuthorizeParameters,omitempty"`
|
||||
|
||||
// allowPasswordGrant, when true, will allow the use of OAuth 2.0's Resource Owner Password Credentials Grant
|
||||
// (see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3) to authenticate to the OIDC provider using a
|
||||
// username and password without a web browser, in addition to the usual browser-based OIDC Authorization Code Flow.
|
||||
// The Resource Owner Password Credentials Grant is not officially part of the OIDC specification, so it may not be
|
||||
// supported by your OIDC provider. If your OIDC provider supports returning ID tokens from a Resource Owner Password
|
||||
// Credentials Grant token request, then you can choose to set this field to true. This will allow end users to choose
|
||||
// to present their username and password to the kubectl CLI (using the Pinniped plugin) to authenticate to the
|
||||
// cluster, without using a web browser to log in as is customary in OIDC Authorization Code Flow. This may be
|
||||
// convenient for users, especially for identities from your OIDC provider which are not intended to represent a human
|
||||
// actor, such as service accounts performing actions in a CI/CD environment. Even if your OIDC provider supports it,
|
||||
// you may wish to disable this behavior by setting this field to false when you prefer to only allow users of this
|
||||
// OIDCIdentityProvider to log in via the browser-based OIDC Authorization Code Flow. Using the Resource Owner Password
|
||||
// Credentials Grant means that the Pinniped CLI and Pinniped Supervisor will directly handle your end users' passwords
|
||||
// (similar to LDAPIdentityProvider), and you will not be able to require multi-factor authentication or use the other
|
||||
// web-based login features of your OIDC provider during Resource Owner Password Credentials Grant logins.
|
||||
// allowPasswordGrant defaults to false.
|
||||
// +optional
|
||||
AllowPasswordGrant bool `json:"allowPasswordGrant,omitempty"`
|
||||
}
|
||||
|
||||
// Parameter is a key/value pair which represents a parameter in an HTTP request.
|
||||
type Parameter struct {
|
||||
// The name of the parameter. Required.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Name string `json:"name"`
|
||||
|
||||
// The value of the parameter.
|
||||
// +optional
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// OIDCClaims provides a mapping from upstream claims into identities.
|
||||
type OIDCClaims struct {
|
||||
// Groups provides the name of the token claim that will be used to ascertain the groups to which
|
||||
// an identity belongs.
|
||||
// Groups provides the name of the ID token claim or userinfo endpoint response claim that will be used to ascertain
|
||||
// the groups to which an identity belongs. By default, the identities will not include any group memberships when
|
||||
// this setting is not configured.
|
||||
// +optional
|
||||
Groups string `json:"groups"`
|
||||
|
||||
// Username provides the name of the token claim that will be used to ascertain an identity's
|
||||
// username.
|
||||
// Username provides the name of the ID token claim or userinfo endpoint response claim that will be used to
|
||||
// ascertain an identity's username. When not set, the username will be an automatically constructed unique string
|
||||
// which will include the issuer URL of your OIDC provider along with the value of the "sub" (subject) claim from
|
||||
// the ID token.
|
||||
// +optional
|
||||
Username string `json:"username"`
|
||||
}
|
||||
@@ -67,7 +150,7 @@ type OIDCClient struct {
|
||||
SecretName string `json:"secretName"`
|
||||
}
|
||||
|
||||
// Spec for configuring an OIDC identity provider.
|
||||
// OIDCIdentityProviderSpec is the spec for configuring an OIDC identity provider.
|
||||
type OIDCIdentityProviderSpec struct {
|
||||
// Issuer is the issuer URL of this OIDC identity provider, i.e., where to fetch
|
||||
// /.well-known/openid-configuration.
|
||||
@@ -113,7 +196,7 @@ type OIDCIdentityProvider struct {
|
||||
Status OIDCIdentityProviderStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// List of OIDCIdentityProvider objects.
|
||||
// OIDCIdentityProviderList lists OIDCIdentityProvider objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type OIDCIdentityProviderList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// IDPType are the strings that can be returned by the Supervisor identity provider discovery endpoint
|
||||
// as the "type" of each returned identity provider.
|
||||
type IDPType string
|
||||
|
||||
// IDPFlow are the strings that can be returned by the Supervisor identity provider discovery endpoint
|
||||
// in the array of allowed client "flows" for each returned identity provider.
|
||||
type IDPFlow string
|
||||
|
||||
const (
|
||||
IDPTypeOIDC IDPType = "oidc"
|
||||
IDPTypeLDAP IDPType = "ldap"
|
||||
IDPTypeActiveDirectory IDPType = "activedirectory"
|
||||
|
||||
IDPFlowCLIPassword IDPFlow = "cli_password"
|
||||
IDPFlowBrowserAuthcode IDPFlow = "browser_authcode"
|
||||
)
|
||||
|
||||
// Equals is a convenience function for comparing an IDPType to a string.
|
||||
func (r IDPType) Equals(s string) bool {
|
||||
return string(r) == s
|
||||
}
|
||||
|
||||
// String is a convenience function to convert an IDPType to a string.
|
||||
func (r IDPType) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
// Equals is a convenience function for comparing an IDPFlow to a string.
|
||||
func (r IDPFlow) Equals(s string) bool {
|
||||
return string(r) == s
|
||||
}
|
||||
|
||||
// String is a convenience function to convert an IDPFlow to a string.
|
||||
func (r IDPFlow) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
// OIDCDiscoveryResponse is part of the response from a FederationDomain's OpenID Provider Configuration
|
||||
// Document returned by the .well-known/openid-configuration endpoint. It ignores all the standard OpenID Provider
|
||||
// configuration metadata and only picks out the portion related to Supervisor identity provider discovery.
|
||||
type OIDCDiscoveryResponse struct {
|
||||
SupervisorDiscovery OIDCDiscoveryResponseIDPEndpoint `json:"discovery.supervisor.pinniped.dev/v1alpha1"`
|
||||
}
|
||||
|
||||
// OIDCDiscoveryResponseIDPEndpoint contains the URL for the identity provider discovery endpoint.
|
||||
type OIDCDiscoveryResponseIDPEndpoint struct {
|
||||
PinnipedIDPsEndpoint string `json:"pinniped_identity_providers_endpoint"`
|
||||
}
|
||||
|
||||
// IDPDiscoveryResponse is the response of a FederationDomain's identity provider discovery endpoint.
|
||||
type IDPDiscoveryResponse struct {
|
||||
PinnipedIDPs []PinnipedIDP `json:"pinniped_identity_providers"`
|
||||
}
|
||||
|
||||
// PinnipedIDP describes a single identity provider as included in the response of a FederationDomain's
|
||||
// identity provider discovery endpoint.
|
||||
type PinnipedIDP struct {
|
||||
Name string `json:"name"`
|
||||
Type IDPType `json:"type"`
|
||||
Flows []IDPFlow `json:"flows,omitempty"`
|
||||
}
|
||||
25
apis/supervisor/oidc/types_supervisor_oidc.go.tmpl
Normal file
25
apis/supervisor/oidc/types_supervisor_oidc.go.tmpl
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package oidc
|
||||
|
||||
// Constants related to the Supervisor FederationDomain's authorization and token endpoints.
|
||||
const (
|
||||
// AuthorizeUsernameHeaderName is the name of the HTTP header which can be used to transmit a username
|
||||
// to the authorize endpoint when using a password flow, for example an OIDCIdentityProvider with a password grant
|
||||
// or an LDAPIdentityProvider.
|
||||
AuthorizeUsernameHeaderName = "Pinniped-Username"
|
||||
|
||||
// AuthorizePasswordHeaderName is the name of the HTTP header which can be used to transmit a password
|
||||
// to the authorize endpoint when using a password flow, for example an OIDCIdentityProvider with a password grant
|
||||
// or an LDAPIdentityProvider.
|
||||
AuthorizePasswordHeaderName = "Pinniped-Password" //nolint:gosec // this is not a credential
|
||||
|
||||
// AuthorizeUpstreamIDPNameParamName is the name of the HTTP request parameter which can be used to help select which
|
||||
// identity provider should be used for authentication by sending the name of the desired identity provider.
|
||||
AuthorizeUpstreamIDPNameParamName = "pinniped_idp_name"
|
||||
|
||||
// AuthorizeUpstreamIDPTypeParamName is the name of the HTTP request parameter which can be used to help select which
|
||||
// identity provider should be used for authentication by sending the type of the desired identity provider.
|
||||
AuthorizeUpstreamIDPTypeParamName = "pinniped_idp_type"
|
||||
)
|
||||
58
cmd/pinniped-concierge-kube-cert-agent/main.go
Normal file
58
cmd/pinniped-concierge-kube-cert-agent/main.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main is the combined entrypoint for the Pinniped "kube-cert-agent" component.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
// this side effect import ensures that we use fipsonly crypto in fips_strict mode.
|
||||
_ "go.pinniped.dev/internal/crypto/ptls"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals // these are swapped during unit tests.
|
||||
var (
|
||||
getenv = os.Getenv
|
||||
fail = log.Fatalf
|
||||
sleep = time.Sleep
|
||||
out = io.Writer(os.Stdout)
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fail("missing subcommand")
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "sleep":
|
||||
sleep(math.MaxInt64)
|
||||
case "print":
|
||||
certBytes, err := ioutil.ReadFile(getenv("CERT_PATH"))
|
||||
if err != nil {
|
||||
fail("could not read CERT_PATH: %v", err)
|
||||
}
|
||||
keyBytes, err := ioutil.ReadFile(getenv("KEY_PATH"))
|
||||
if err != nil {
|
||||
fail("could not read KEY_PATH: %v", err)
|
||||
}
|
||||
if err := json.NewEncoder(out).Encode(&struct {
|
||||
Cert string `json:"tls.crt"`
|
||||
Key string `json:"tls.key"`
|
||||
}{
|
||||
Cert: base64.StdEncoding.EncodeToString(certBytes),
|
||||
Key: base64.StdEncoding.EncodeToString(keyBytes),
|
||||
}); err != nil {
|
||||
fail("failed to write output: %v", err)
|
||||
}
|
||||
default:
|
||||
fail("invalid subcommand %q", os.Args[1])
|
||||
}
|
||||
}
|
||||
128
cmd/pinniped-concierge-kube-cert-agent/main_test.go
Normal file
128
cmd/pinniped-concierge-kube-cert-agent/main_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type errWriter struct{}
|
||||
|
||||
func (e errWriter) Write([]byte) (int, error) { return 0, fmt.Errorf("some write error") }
|
||||
|
||||
func TestEntrypoint(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
args []string
|
||||
env map[string]string
|
||||
failOutput bool
|
||||
wantSleep time.Duration
|
||||
wantLog string
|
||||
wantOutJSON string
|
||||
wantFail bool
|
||||
}{
|
||||
{
|
||||
name: "missing args",
|
||||
args: []string{},
|
||||
wantLog: "missing subcommand\n",
|
||||
wantFail: true,
|
||||
},
|
||||
{
|
||||
name: "invalid subcommand",
|
||||
args: []string{"/path/to/binary", "invalid"},
|
||||
wantLog: "invalid subcommand \"invalid\"\n",
|
||||
wantFail: true,
|
||||
},
|
||||
{
|
||||
name: "valid sleep",
|
||||
args: []string{"/path/to/binary", "sleep"},
|
||||
wantSleep: 2562047*time.Hour + 47*time.Minute + 16*time.Second + 854775807*time.Nanosecond, // math.MaxInt64 nanoseconds, approximately 290 years
|
||||
},
|
||||
{
|
||||
name: "missing cert file",
|
||||
args: []string{"/path/to/binary", "print"},
|
||||
env: map[string]string{
|
||||
"CERT_PATH": "./does/not/exist",
|
||||
"KEY_PATH": "./testdata/test.key",
|
||||
},
|
||||
wantFail: true,
|
||||
wantLog: "could not read CERT_PATH: open ./does/not/exist: no such file or directory\n",
|
||||
},
|
||||
{
|
||||
name: "missing key file",
|
||||
args: []string{"/path/to/binary", "print"},
|
||||
env: map[string]string{
|
||||
"CERT_PATH": "./testdata/test.crt",
|
||||
"KEY_PATH": "./does/not/exist",
|
||||
},
|
||||
wantFail: true,
|
||||
wantLog: "could not read KEY_PATH: open ./does/not/exist: no such file or directory\n",
|
||||
},
|
||||
{
|
||||
name: "fail to write output",
|
||||
args: []string{"/path/to/binary", "print"},
|
||||
env: map[string]string{
|
||||
"CERT_PATH": "./testdata/test.crt",
|
||||
"KEY_PATH": "./testdata/test.key",
|
||||
},
|
||||
failOutput: true,
|
||||
wantFail: true,
|
||||
wantLog: "failed to write output: some write error\n",
|
||||
},
|
||||
{
|
||||
name: "successful print",
|
||||
args: []string{"/path/to/binary", "print"},
|
||||
env: map[string]string{
|
||||
"CERT_PATH": "./testdata/test.crt",
|
||||
"KEY_PATH": "./testdata/test.key",
|
||||
},
|
||||
wantOutJSON: `{
|
||||
"tls.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EY3lOVEl4TURReE9Gb1hEVE13TURjeU16SXhNRFF4T0Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTDNLCmhZdjJnSVExRHd6aDJjV01pZCtvZkFudkxJZlYyWHY2MXZUTEdwclVJK1hVcUI0L2d0ZjZYNlVObjBMZXR0Mm4KZDhwNHd5N2h3NzNoVS9nZ2R2bVdKdnFCclNqYzNKR2Z5K2tqNjZmS1hYK1BUbGJMN1Fid2lSdmNTcUlYSVdsVgpsSEh4RUNXckVEOGpDdWx3L05WcWZvb2svaDVpTlVDVDl5c3dTSnIvMGZJbWlWbm9UbElvRVlHMmVDTmVqWjVjCmczOXVEM1pUcWQ5WnhXd1NMTG5JKzJrcEpuWkJQY2QxWlE4QVFxekRnWnRZUkNxYWNuNWdja1FVS1pXS1FseG8KRWZ0NmcxWEhKb3VBV0FadzdoRXRrMHY4ckcwL2VLRjd3YW14Rmk2QkZWbGJqV0JzQjRUOXJBcGJkQldUS2VDSgpIdjhmdjVSTUZTenBUM3V6VE84Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDaDVSaGJ4cUplK1ovZ2MxN2NaaEtObWRpd3UKSTJwTHAzUUJmd3ZOK1dibWFqencvN3JZaFkwZDhKWVZUSnpYU0NQV2k2VUFLeEF0WE9MRjhXSUlmOWkzOW42Ugp1S09CR1cxNEZ6ekd5UkppRDNxYUcvSlR2RVcrU0xod2w2OE5kcjVMSFNuYnVnQXFxMzFhYmNReTZabDl2NUE4CkpLQzk3TGovU244cmo3b3BLeTRXM29xN05DUXNBYjB6aDRJbGxSRjZVdlNuSnlTZnNnN3hkWEhIcHhZREh0T1MKWGNPdTV5U1VJWlRnRmU5UmZlVVpsR1o1eG4wY2tNbFE3cVcyV3gxcTBPVld3NXVzNE50a0dxS3JIRzRUbjFYNwp1d28vWXl0bjVzRHhyRHYxL29paTZBWk9Dc1RQcmU0b0Qzd3o0bm1WekNWSmNncnFINFEyNGhUOFdOZz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
|
||||
"tls.key": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBdmNxRmkvYUFoRFVQRE9IWnhZeUozNmg4Q2U4c2g5WFplL3JXOU1zYW10UWo1ZFNvCkhqK0MxL3BmcFEyZlF0NjIzYWQzeW5qREx1SER2ZUZUK0NCMitaWW0rb0d0S056Y2taL0w2U1BycDhwZGY0OU8KVnN2dEJ2Q0pHOXhLb2hjaGFWV1VjZkVRSmFzUVB5TUs2WEQ4MVdwK2lpVCtIbUkxUUpQM0t6Qkltdi9SOGlhSgpXZWhPVWlnUmdiWjRJMTZObmx5RGYyNFBkbE9wMzFuRmJCSXN1Y2o3YVNrbWRrRTl4M1ZsRHdCQ3JNT0JtMWhFCktwcHlmbUJ5UkJRcGxZcENYR2dSKzNxRFZjY21pNEJZQm5EdUVTMlRTL3lzYlQ5NG9YdkJxYkVXTG9FVldWdU4KWUd3SGhQMnNDbHQwRlpNcDRJa2UveCsvbEV3VkxPbFBlN05NN3dJREFRQUJBb0lCQUZDMXRVRW1ITlVjTTBCSgpNM0Q5S1F6Qis2M0YxbXdWbHgxUU9PVjFFZVZSM2NvNU94MVI2UFNyOXN5Y0ZHUTlqZ3FJMHpwNVRKZTlUcDZMCkdraGtsZlBoMU1Xbks5bzZ3bG56V0tYV3JycDJKbmkrbXBQeXVPUEFtcTRNYW5pdjJYZVArMGJST3dxcHlvanYKQUE3eUM3TStUSDIyNlpKR05WczNFVjkrY3dIbWwweXV6QmZJSm4vcnYvdzJnK1dSS00vTUMwUzdrMmQ4YlJsQQpOeWNLVkdBR0JoS1RsdGpvVllPZWg2YUhFcFNqSzh6ZmFlUGpvNWRZSnZvVklsaTYwWUNnY0pPVS84alhUK05wCjFGbTd0UnZBdGozcFVwMFNxZGFmMlJVemg5amZKcDJWRkNIdVNKNlRQcUFyT3lRb2p0TWNUSEYwVGlXN3hySFAKeE9DUklBRUNnWUVBd0dCUFU3dmR0aE1KQmcrT1JVb0dRUWFJdFRlSnZRd0lxSnZiS0Qyb3NwNGpoUzFkR1pCdwpXMzBHS0VjL2dkOEpOdE9xOUJCbk1pY1BGN2hrdHV5K2JTUHY0MVhQdWQ2N3JTU083VHN3MjBDMTBnRlJxMDZCCnpJSldGQVVxSzNJa3ZWYzNWRG10U0xTRG94NFFaL0JkcWFNbFE1eTVKQ3NDNWtUaG1rWkZsTzhDZ1lFQS9JOVgKWUhpNlJpb01KRTFmcU9ISkw0RERqbGV6bWN1UnJEN2ZFNUluS2J0SloySmhHWU9YL0MwS1huSFRPV1RDRHh4TgpGQnZwdkQ2WHY1bzNQaEI5WjZrMmZxdko0R1M4dXJrRy9LVTR4Y0MrYmFrKzlhdmE4b2FpU3FHMTZ6RDlOSDJQCmpKNjBOcmJMbDFKMHBVOWZpd3VGVlVLSjRoRFpPZk45UnFZZHlBRUNnWUFWd284V2hKaUdnTTZ6ZmN6MDczT1gKcFZxUFRQSHFqVkxwWjMrNXBJZlJkR3ZHSTZSMVFNNUV1dmFZVmI3TVBPTTQ3V1pYNXdjVk9DL1AyZzZpVmxNUAoyMUhHSUMyMzg0YTlCZmFZeE9vNDBxLytTaUhudzZDUTlta3dLSWxsa3Fxdk5BOVJHcGtNTVViMmkyOEZvcjJsCmM0dkNneGE2RFpkdFhuczZUUnFQeHdLQmdDZlk1Y3hPdi9UNkJWaGs3TWJVZU0ySjMxREIvWkF5VWhWL0Jlc3MKa0FsQmgxOU1ZazJJT1o2TDdLcmlBcFYzbERhV0hJTWp0RWtEQnlZdnlxOThJbzBNWVpDeXdmTXBjYTEwSytvSQpsMkI3L0krSXVHcENaeFVFc081ZGZUcFNUR0RQdnFwTkQ5bmlGVlVXcVZpN29UTnE2ZXA5eVF0bDVTQURqcXhxCjRTQUJBb0dBSW0waFVnMXd0Y1M0NmNHTHk2UElrUE01dG9jVFNnaHR6NHZGc3VrL2k0UUE5R0JvQk8yZ0g2dHkKK2tKSG1lYVh0MmRtZ3lTcDBRQVdpdDVVbGNlRXVtQjBOWG5BZEpaUXhlR1NGU3lZa0RXaHdYZDh3RGNlS28vMQpMZkNVNkRrOElOL1NzcHBWVVdYUTJybE9SdnhsckhlQ2lvOG8wa1M5WWl1NTVXTVlnNGc9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg=="
|
||||
}`,
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var logBuf bytes.Buffer
|
||||
testLog := log.New(&logBuf, "", 0)
|
||||
exited := "exiting via fatal"
|
||||
fail = func(format string, v ...interface{}) {
|
||||
testLog.Printf(format, v...)
|
||||
panic(exited)
|
||||
}
|
||||
|
||||
var sawSleep time.Duration
|
||||
sleep = func(d time.Duration) { sawSleep = d }
|
||||
|
||||
var sawOutput bytes.Buffer
|
||||
out = &sawOutput
|
||||
if tt.failOutput {
|
||||
out = &errWriter{}
|
||||
}
|
||||
|
||||
os.Args = tt.args
|
||||
getenv = func(key string) string { return tt.env[key] }
|
||||
if tt.wantFail {
|
||||
require.PanicsWithValue(t, exited, main)
|
||||
} else {
|
||||
require.NotPanics(t, main)
|
||||
}
|
||||
require.Equal(t, tt.wantSleep.String(), sawSleep.String())
|
||||
require.Equal(t, tt.wantLog, logBuf.String())
|
||||
if tt.wantOutJSON == "" {
|
||||
require.Empty(t, sawOutput.String())
|
||||
} else {
|
||||
require.JSONEq(t, tt.wantOutJSON, sawOutput.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
17
cmd/pinniped-concierge-kube-cert-agent/testdata/test.crt
vendored
Normal file
17
cmd/pinniped-concierge-kube-cert-agent/testdata/test.crt
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
|
||||
cm5ldGVzMB4XDTIwMDcyNTIxMDQxOFoXDTMwMDcyMzIxMDQxOFowFTETMBEGA1UE
|
||||
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3K
|
||||
hYv2gIQ1Dwzh2cWMid+ofAnvLIfV2Xv61vTLGprUI+XUqB4/gtf6X6UNn0Lett2n
|
||||
d8p4wy7hw73hU/ggdvmWJvqBrSjc3JGfy+kj66fKXX+PTlbL7QbwiRvcSqIXIWlV
|
||||
lHHxECWrED8jCulw/NVqfook/h5iNUCT9yswSJr/0fImiVnoTlIoEYG2eCNejZ5c
|
||||
g39uD3ZTqd9ZxWwSLLnI+2kpJnZBPcd1ZQ8AQqzDgZtYRCqacn5gckQUKZWKQlxo
|
||||
Eft6g1XHJouAWAZw7hEtk0v8rG0/eKF7wamxFi6BFVlbjWBsB4T9rApbdBWTKeCJ
|
||||
Hv8fv5RMFSzpT3uzTO8CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACh5RhbxqJe+Z/gc17cZhKNmdiwu
|
||||
I2pLp3QBfwvN+Wbmajzw/7rYhY0d8JYVTJzXSCPWi6UAKxAtXOLF8WIIf9i39n6R
|
||||
uKOBGW14FzzGyRJiD3qaG/JTvEW+SLhwl68Ndr5LHSnbugAqq31abcQy6Zl9v5A8
|
||||
JKC97Lj/Sn8rj7opKy4W3oq7NCQsAb0zh4IllRF6UvSnJySfsg7xdXHHpxYDHtOS
|
||||
XcOu5ySUIZTgFe9RfeUZlGZ5xn0ckMlQ7qW2Wx1q0OVWw5us4NtkGqKrHG4Tn1X7
|
||||
uwo/Yytn5sDxrDv1/oii6AZOCsTPre4oD3wz4nmVzCVJcgrqH4Q24hT8WNg=
|
||||
-----END CERTIFICATE-----
|
||||
27
cmd/pinniped-concierge-kube-cert-agent/testdata/test.key
vendored
Normal file
27
cmd/pinniped-concierge-kube-cert-agent/testdata/test.key
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAvcqFi/aAhDUPDOHZxYyJ36h8Ce8sh9XZe/rW9MsamtQj5dSo
|
||||
Hj+C1/pfpQ2fQt623ad3ynjDLuHDveFT+CB2+ZYm+oGtKNzckZ/L6SPrp8pdf49O
|
||||
VsvtBvCJG9xKohchaVWUcfEQJasQPyMK6XD81Wp+iiT+HmI1QJP3KzBImv/R8iaJ
|
||||
WehOUigRgbZ4I16NnlyDf24PdlOp31nFbBIsucj7aSkmdkE9x3VlDwBCrMOBm1hE
|
||||
KppyfmByRBQplYpCXGgR+3qDVccmi4BYBnDuES2TS/ysbT94oXvBqbEWLoEVWVuN
|
||||
YGwHhP2sClt0FZMp4Ike/x+/lEwVLOlPe7NM7wIDAQABAoIBAFC1tUEmHNUcM0BJ
|
||||
M3D9KQzB+63F1mwVlx1QOOV1EeVR3co5Ox1R6PSr9sycFGQ9jgqI0zp5TJe9Tp6L
|
||||
GkhklfPh1MWnK9o6wlnzWKXWrrp2Jni+mpPyuOPAmq4Maniv2XeP+0bROwqpyojv
|
||||
AA7yC7M+TH226ZJGNVs3EV9+cwHml0yuzBfIJn/rv/w2g+WRKM/MC0S7k2d8bRlA
|
||||
NycKVGAGBhKTltjoVYOeh6aHEpSjK8zfaePjo5dYJvoVIli60YCgcJOU/8jXT+Np
|
||||
1Fm7tRvAtj3pUp0Sqdaf2RUzh9jfJp2VFCHuSJ6TPqArOyQojtMcTHF0TiW7xrHP
|
||||
xOCRIAECgYEAwGBPU7vdthMJBg+ORUoGQQaItTeJvQwIqJvbKD2osp4jhS1dGZBw
|
||||
W30GKEc/gd8JNtOq9BBnMicPF7hktuy+bSPv41XPud67rSSO7Tsw20C10gFRq06B
|
||||
zIJWFAUqK3IkvVc3VDmtSLSDox4QZ/BdqaMlQ5y5JCsC5kThmkZFlO8CgYEA/I9X
|
||||
YHi6RioMJE1fqOHJL4DDjlezmcuRrD7fE5InKbtJZ2JhGYOX/C0KXnHTOWTCDxxN
|
||||
FBvpvD6Xv5o3PhB9Z6k2fqvJ4GS8urkG/KU4xcC+bak+9ava8oaiSqG16zD9NH2P
|
||||
jJ60NrbLl1J0pU9fiwuFVUKJ4hDZOfN9RqYdyAECgYAVwo8WhJiGgM6zfcz073OX
|
||||
pVqPTPHqjVLpZ3+5pIfRdGvGI6R1QM5EuvaYVb7MPOM47WZX5wcVOC/P2g6iVlMP
|
||||
21HGIC2384a9BfaYxOo40q/+SiHnw6CQ9mkwKIllkqqvNA9RGpkMMUb2i28For2l
|
||||
c4vCgxa6DZdtXns6TRqPxwKBgCfY5cxOv/T6BVhk7MbUeM2J31DB/ZAyUhV/Bess
|
||||
kAlBh19MYk2IOZ6L7KriApV3lDaWHIMjtEkDByYvyq98Io0MYZCywfMpca10K+oI
|
||||
l2B7/I+IuGpCZxUEsO5dfTpSTGDPvqpND9niFVUWqVi7oTNq6ep9yQtl5SADjqxq
|
||||
4SABAoGAIm0hUg1wtcS46cGLy6PIkPM5tocTSghtz4vFsuk/i4QA9GBoBO2gH6ty
|
||||
+kJHmeaXt2dmgySp0QAWit5UlceEumB0NXnAdJZQxeGSFSyYkDWhwXd8wDceKo/1
|
||||
LfCU6Dk8IN/SsppVUWXQ2rlORvxlrHeCio8o0kS9Yiu55WMYg4g=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/client-go/pkg/version"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/component-base/logs"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"go.pinniped.dev/internal/concierge/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logs.InitLogs()
|
||||
defer logs.FlushLogs()
|
||||
|
||||
// Dump out the time since compile (mostly useful for benchmarking our local development cycle latency).
|
||||
var timeSinceCompile time.Duration
|
||||
if buildDate, err := time.Parse(time.RFC3339, version.Get().BuildDate); err == nil {
|
||||
timeSinceCompile = time.Since(buildDate).Round(time.Second)
|
||||
}
|
||||
klog.Infof("Running %s at %#v (%s since build)", rest.DefaultKubernetesUserAgent(), version.Get(), timeSinceCompile)
|
||||
|
||||
ctx := genericapiserver.SetupSignalContext()
|
||||
|
||||
if err := server.New(ctx, os.Args[1:], os.Stdout, os.Stderr).Run(); err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
}
|
||||
43
cmd/pinniped-server/main.go
Normal file
43
cmd/pinniped-server/main.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main is the combined entrypoint for all Pinniped server components.
|
||||
//
|
||||
// It dispatches to the appropriate Main() entrypoint based the name it is invoked as (os.Args[0]). In our server
|
||||
// container image, this binary is symlinked to several names such as `/usr/local/bin/pinniped-concierge`.
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
concierge "go.pinniped.dev/internal/concierge/server"
|
||||
// this side effect import ensures that we use fipsonly crypto in fips_strict mode.
|
||||
_ "go.pinniped.dev/internal/crypto/ptls"
|
||||
lua "go.pinniped.dev/internal/localuserauthenticator"
|
||||
supervisor "go.pinniped.dev/internal/supervisor/server"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals // these are swapped during unit tests.
|
||||
var (
|
||||
fail = klog.Fatalf
|
||||
subcommands = map[string]func(){
|
||||
"pinniped-concierge": concierge.Main,
|
||||
"pinniped-supervisor": supervisor.Main,
|
||||
"local-user-authenticator": lua.Main,
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 0 {
|
||||
fail("missing os.Args")
|
||||
}
|
||||
binary := filepath.Base(os.Args[0])
|
||||
if subcommands[binary] == nil {
|
||||
fail("must be invoked as one of %v, not %q", sets.StringKeySet(subcommands).List(), binary)
|
||||
}
|
||||
subcommands[binary]()
|
||||
}
|
||||
72
cmd/pinniped-server/main_test.go
Normal file
72
cmd/pinniped-server/main_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEntrypoint(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
args []string
|
||||
wantOutput string
|
||||
wantFail bool
|
||||
wantArgs []string
|
||||
}{
|
||||
{
|
||||
name: "missing args",
|
||||
args: []string{},
|
||||
wantOutput: "missing os.Args\n",
|
||||
wantFail: true,
|
||||
},
|
||||
{
|
||||
name: "invalid subcommand",
|
||||
args: []string{"/path/to/invalid", "some", "args"},
|
||||
wantOutput: "must be invoked as one of [another-test-binary valid-test-binary], not \"invalid\"\n",
|
||||
wantFail: true,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
args: []string{"/path/to/valid-test-binary", "foo", "bar"},
|
||||
wantArgs: []string{"/path/to/valid-test-binary", "foo", "bar"},
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var logBuf bytes.Buffer
|
||||
testLog := log.New(&logBuf, "", 0)
|
||||
exited := "exiting via fatal"
|
||||
fail = func(format string, v ...interface{}) {
|
||||
testLog.Printf(format, v...)
|
||||
panic(exited)
|
||||
}
|
||||
|
||||
// Make a test command that records os.Args when it's invoked.
|
||||
var gotArgs []string
|
||||
subcommands = map[string]func(){
|
||||
"valid-test-binary": func() { gotArgs = os.Args },
|
||||
"another-test-binary": func() {},
|
||||
}
|
||||
|
||||
os.Args = tt.args
|
||||
if tt.wantFail {
|
||||
require.PanicsWithValue(t, exited, main)
|
||||
} else {
|
||||
require.NotPanics(t, main)
|
||||
}
|
||||
if tt.wantArgs != nil {
|
||||
require.Equal(t, tt.wantArgs, gotArgs)
|
||||
}
|
||||
if tt.wantOutput != "" {
|
||||
require.Equal(t, tt.wantOutput, logBuf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -28,12 +27,13 @@ import (
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/transport"
|
||||
|
||||
conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
idpdiscoveryv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
||||
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
"go.pinniped.dev/internal/net/phttp"
|
||||
)
|
||||
|
||||
type kubeconfigDeps struct {
|
||||
@@ -61,12 +61,14 @@ type getKubeconfigOIDCParams struct {
|
||||
listenPort uint16
|
||||
scopes []string
|
||||
skipBrowser bool
|
||||
skipListen bool
|
||||
sessionCachePath string
|
||||
debugSessionCache bool
|
||||
caBundle caBundleFlag
|
||||
requestAudience string
|
||||
upstreamIDPName string
|
||||
upstreamIDPType string
|
||||
upstreamIDPFlow string
|
||||
}
|
||||
|
||||
type getKubeconfigConciergeParams struct {
|
||||
@@ -94,23 +96,7 @@ type getKubeconfigParams struct {
|
||||
generatedNameSuffix string
|
||||
credentialCachePath string
|
||||
credentialCachePathSet bool
|
||||
}
|
||||
|
||||
type supervisorOIDCDiscoveryResponseWithV1Alpha1 struct {
|
||||
SupervisorDiscovery SupervisorDiscoveryResponseV1Alpha1 `json:"discovery.supervisor.pinniped.dev/v1alpha1"`
|
||||
}
|
||||
|
||||
type SupervisorDiscoveryResponseV1Alpha1 struct {
|
||||
PinnipedIDPsEndpoint string `json:"pinniped_identity_providers_endpoint"`
|
||||
}
|
||||
|
||||
type supervisorIDPsDiscoveryResponseV1Alpha1 struct {
|
||||
PinnipedIDPs []pinnipedIDPResponse `json:"pinniped_identity_providers"`
|
||||
}
|
||||
|
||||
type pinnipedIDPResponse struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
installHint string
|
||||
}
|
||||
|
||||
func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
||||
@@ -146,12 +132,14 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
||||
f.Uint16Var(&flags.oidc.listenPort, "oidc-listen-port", 0, "TCP port for localhost listener (authorization code flow only)")
|
||||
f.StringSliceVar(&flags.oidc.scopes, "oidc-scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped:request-audience"}, "OpenID Connect scopes to request during login")
|
||||
f.BoolVar(&flags.oidc.skipBrowser, "oidc-skip-browser", false, "During OpenID Connect login, skip opening the browser (just print the URL)")
|
||||
f.BoolVar(&flags.oidc.skipListen, "oidc-skip-listen", false, "During OpenID Connect login, skip starting a localhost callback listener (manual copy/paste flow only)")
|
||||
f.StringVar(&flags.oidc.sessionCachePath, "oidc-session-cache", "", "Path to OpenID Connect session cache file")
|
||||
f.Var(&flags.oidc.caBundle, "oidc-ca-bundle", "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)")
|
||||
f.BoolVar(&flags.oidc.debugSessionCache, "oidc-debug-session-cache", false, "Print debug logs related to the OpenID Connect session cache")
|
||||
f.StringVar(&flags.oidc.requestAudience, "oidc-request-audience", "", "Request a token with an alternate audience using RFC8693 token exchange")
|
||||
f.StringVar(&flags.oidc.upstreamIDPName, "upstream-identity-provider-name", "", "The name of the upstream identity provider used during login with a Supervisor")
|
||||
f.StringVar(&flags.oidc.upstreamIDPType, "upstream-identity-provider-type", "", "The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap')")
|
||||
f.StringVar(&flags.oidc.upstreamIDPType, "upstream-identity-provider-type", "", fmt.Sprintf("The type of the upstream identity provider used during login with a Supervisor (e.g. '%s', '%s', '%s')", idpdiscoveryv1alpha1.IDPTypeOIDC, idpdiscoveryv1alpha1.IDPTypeLDAP, idpdiscoveryv1alpha1.IDPTypeActiveDirectory))
|
||||
f.StringVar(&flags.oidc.upstreamIDPFlow, "upstream-identity-provider-flow", "", fmt.Sprintf("The type of client flow to use with the upstream identity provider during login with a Supervisor (e.g. '%s', '%s')", idpdiscoveryv1alpha1.IDPFlowCLIPassword, idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode))
|
||||
f.StringVar(&flags.kubeconfigPath, "kubeconfig", os.Getenv("KUBECONFIG"), "Path to kubeconfig file")
|
||||
f.StringVar(&flags.kubeconfigContextOverride, "kubeconfig-context", "", "Kubeconfig context name (default: current active context)")
|
||||
f.BoolVar(&flags.skipValidate, "skip-validation", false, "Skip final validation of the kubeconfig (default: false)")
|
||||
@@ -159,8 +147,12 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
||||
f.StringVarP(&flags.outputPath, "output", "o", "", "Output file path (default: stdout)")
|
||||
f.StringVar(&flags.generatedNameSuffix, "generated-name-suffix", "-pinniped", "Suffix to append to generated cluster, context, user kubeconfig entries")
|
||||
f.StringVar(&flags.credentialCachePath, "credential-cache", "", "Path to cluster-specific credentials cache")
|
||||
f.StringVar(&flags.installHint, "install-hint", "The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli for more details", "This text is shown to the user when the pinniped CLI is not installed.")
|
||||
mustMarkHidden(cmd, "oidc-debug-session-cache")
|
||||
|
||||
// --oidc-skip-listen is mainly needed for testing. We'll leave it hidden until we have a non-testing use case.
|
||||
mustMarkHidden(cmd, "oidc-skip-listen")
|
||||
|
||||
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
|
||||
mustMarkHidden(cmd, "concierge-namespace")
|
||||
|
||||
@@ -179,7 +171,6 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, flags getKubeconfigParams) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, flags.timeout)
|
||||
defer cancel()
|
||||
@@ -238,8 +229,10 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
|
||||
cluster.CertificateAuthorityData = flags.concierge.caBundle
|
||||
}
|
||||
|
||||
// If there is an issuer, and if both upstream flags are not already set, then try to discover Supervisor upstream IDP.
|
||||
if len(flags.oidc.issuer) > 0 && (flags.oidc.upstreamIDPType == "" || flags.oidc.upstreamIDPName == "") {
|
||||
// If there is an issuer, and if any upstream IDP flags are not already set, then try to discover Supervisor upstream IDP details.
|
||||
// When all the upstream IDP flags are set by the user, then skip discovery and don't validate their input. Maybe they know something
|
||||
// that we can't know, like the name of an IDP that they are going to define in the future.
|
||||
if len(flags.oidc.issuer) > 0 && (flags.oidc.upstreamIDPType == "" || flags.oidc.upstreamIDPName == "" || flags.oidc.upstreamIDPFlow == "") {
|
||||
if err := discoverSupervisorUpstreamIDP(ctx, &flags); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -266,6 +259,7 @@ func newExecConfig(deps kubeconfigDeps, flags getKubeconfigParams) (*clientcmdap
|
||||
ProvideClusterInfo: true,
|
||||
}
|
||||
|
||||
execConfig.InstallHint = flags.installHint
|
||||
var err error
|
||||
execConfig.Command, err = deps.getPathToSelf()
|
||||
if err != nil {
|
||||
@@ -317,6 +311,9 @@ func newExecConfig(deps kubeconfigDeps, flags getKubeconfigParams) (*clientcmdap
|
||||
if flags.oidc.skipBrowser {
|
||||
execConfig.Args = append(execConfig.Args, "--skip-browser")
|
||||
}
|
||||
if flags.oidc.skipListen {
|
||||
execConfig.Args = append(execConfig.Args, "--skip-listen")
|
||||
}
|
||||
if flags.oidc.listenPort != 0 {
|
||||
execConfig.Args = append(execConfig.Args, "--listen-port="+strconv.Itoa(int(flags.oidc.listenPort)))
|
||||
}
|
||||
@@ -338,6 +335,9 @@ func newExecConfig(deps kubeconfigDeps, flags getKubeconfigParams) (*clientcmdap
|
||||
if flags.oidc.upstreamIDPType != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--upstream-identity-provider-type="+flags.oidc.upstreamIDPType)
|
||||
}
|
||||
if flags.oidc.upstreamIDPFlow != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--upstream-identity-provider-flow="+flags.oidc.upstreamIDPFlow)
|
||||
}
|
||||
|
||||
return execConfig, nil
|
||||
}
|
||||
@@ -662,17 +662,8 @@ func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconf
|
||||
return fmt.Errorf("invalid kubeconfig (no certificateAuthorityData)")
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
RootCAs: kubeconfigCA,
|
||||
},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
},
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
httpClient := phttp.Default(kubeconfigCA)
|
||||
httpClient.Timeout = 10 * time.Second
|
||||
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
@@ -723,7 +714,7 @@ func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconf
|
||||
func countCACerts(pemData []byte) int {
|
||||
pool := x509.NewCertPool()
|
||||
pool.AppendCertsFromPEM(pemData)
|
||||
return len(pool.Subjects())
|
||||
return len(pool.Subjects()) // nolint: staticcheck // not system cert pool
|
||||
}
|
||||
|
||||
func hasPendingStrategy(credentialIssuer *configv1alpha1.CredentialIssuer) bool {
|
||||
@@ -750,40 +741,43 @@ func discoverSupervisorUpstreamIDP(ctx context.Context, flags *getKubeconfigPara
|
||||
return nil
|
||||
}
|
||||
|
||||
upstreamIDPs, err := discoverAllAvailableSupervisorUpstreamIDPs(ctx, pinnipedIDPsEndpoint, httpClient)
|
||||
discoveredUpstreamIDPs, err := discoverAllAvailableSupervisorUpstreamIDPs(ctx, pinnipedIDPsEndpoint, httpClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(upstreamIDPs) == 1 {
|
||||
flags.oidc.upstreamIDPName = upstreamIDPs[0].Name
|
||||
flags.oidc.upstreamIDPType = upstreamIDPs[0].Type
|
||||
} else if len(upstreamIDPs) > 1 {
|
||||
idpName, idpType, err := selectUpstreamIDP(upstreamIDPs, flags.oidc.upstreamIDPName, flags.oidc.upstreamIDPType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flags.oidc.upstreamIDPName = idpName
|
||||
flags.oidc.upstreamIDPType = idpType
|
||||
|
||||
if len(discoveredUpstreamIDPs) == 0 {
|
||||
// Discovered that the Supervisor does not have any upstream IDPs defined. Continue without putting one into the
|
||||
// kubeconfig. This kubeconfig will only work if the user defines one (and only one) OIDC IDP in the Supervisor
|
||||
// later and wants to use the default client flow for OIDC (browser-based auth).
|
||||
return nil
|
||||
}
|
||||
|
||||
selectedIDPName, selectedIDPType, discoveredIDPFlows, err := selectUpstreamIDPNameAndType(discoveredUpstreamIDPs, flags.oidc.upstreamIDPName, flags.oidc.upstreamIDPType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selectedIDPFlow, err := selectUpstreamIDPFlow(discoveredIDPFlows, selectedIDPName, selectedIDPType, flags.oidc.upstreamIDPFlow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flags.oidc.upstreamIDPName = selectedIDPName
|
||||
flags.oidc.upstreamIDPType = selectedIDPType.String()
|
||||
flags.oidc.upstreamIDPFlow = selectedIDPFlow.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDiscoveryHTTPClient(caBundleFlag caBundleFlag) (*http.Client, error) {
|
||||
t := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
httpClient := &http.Client{Transport: t}
|
||||
var rootCAs *x509.CertPool
|
||||
if caBundleFlag != nil {
|
||||
rootCAs := x509.NewCertPool()
|
||||
ok := rootCAs.AppendCertsFromPEM(caBundleFlag)
|
||||
if !ok {
|
||||
rootCAs = x509.NewCertPool()
|
||||
if ok := rootCAs.AppendCertsFromPEM(caBundleFlag); !ok {
|
||||
return nil, fmt.Errorf("unable to fetch OIDC discovery data from issuer: could not parse CA bundle")
|
||||
}
|
||||
t.TLSClientConfig.RootCAs = rootCAs
|
||||
}
|
||||
httpClient.Transport = transport.DebugWrappers(httpClient.Transport)
|
||||
return httpClient, nil
|
||||
return phttp.Default(rootCAs), nil
|
||||
}
|
||||
|
||||
func discoverIDPsDiscoveryEndpointURL(ctx context.Context, issuer string, httpClient *http.Client) (string, error) {
|
||||
@@ -792,7 +786,7 @@ func discoverIDPsDiscoveryEndpointURL(ctx context.Context, issuer string, httpCl
|
||||
return "", fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
|
||||
}
|
||||
|
||||
var body supervisorOIDCDiscoveryResponseWithV1Alpha1
|
||||
var body idpdiscoveryv1alpha1.OIDCDiscoveryResponse
|
||||
err = discoveredProvider.Claims(&body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
|
||||
@@ -801,7 +795,7 @@ func discoverIDPsDiscoveryEndpointURL(ctx context.Context, issuer string, httpCl
|
||||
return body.SupervisorDiscovery.PinnipedIDPsEndpoint, nil
|
||||
}
|
||||
|
||||
func discoverAllAvailableSupervisorUpstreamIDPs(ctx context.Context, pinnipedIDPsEndpoint string, httpClient *http.Client) ([]pinnipedIDPResponse, error) {
|
||||
func discoverAllAvailableSupervisorUpstreamIDPs(ctx context.Context, pinnipedIDPsEndpoint string, httpClient *http.Client) ([]idpdiscoveryv1alpha1.PinnipedIDP, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, pinnipedIDPsEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while forming request to IDP discovery URL: %w", err)
|
||||
@@ -823,7 +817,7 @@ func discoverAllAvailableSupervisorUpstreamIDPs(ctx context.Context, pinnipedIDP
|
||||
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: could not read response body: %w", err)
|
||||
}
|
||||
|
||||
var body supervisorIDPsDiscoveryResponseV1Alpha1
|
||||
var body idpdiscoveryv1alpha1.IDPDiscoveryResponse
|
||||
err = json.Unmarshal(rawBody, &body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: could not parse response JSON: %w", err)
|
||||
@@ -832,53 +826,106 @@ func discoverAllAvailableSupervisorUpstreamIDPs(ctx context.Context, pinnipedIDP
|
||||
return body.PinnipedIDPs, nil
|
||||
}
|
||||
|
||||
func selectUpstreamIDP(pinnipedIDPs []pinnipedIDPResponse, idpName, idpType string) (string, string, error) {
|
||||
func selectUpstreamIDPNameAndType(pinnipedIDPs []idpdiscoveryv1alpha1.PinnipedIDP, specifiedIDPName, specifiedIDPType string) (string, idpdiscoveryv1alpha1.IDPType, []idpdiscoveryv1alpha1.IDPFlow, error) {
|
||||
pinnipedIDPsString, _ := json.Marshal(pinnipedIDPs)
|
||||
var discoveredFlows []idpdiscoveryv1alpha1.IDPFlow
|
||||
switch {
|
||||
case idpType != "":
|
||||
discoveredName := ""
|
||||
case specifiedIDPName != "" && specifiedIDPType != "":
|
||||
// The user specified both name and type, so check to see if there exists an exact match.
|
||||
for _, idp := range pinnipedIDPs {
|
||||
if idp.Type == idpType {
|
||||
if idp.Name == specifiedIDPName && idp.Type.Equals(specifiedIDPType) {
|
||||
return specifiedIDPName, idp.Type, idp.Flows, nil
|
||||
}
|
||||
}
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"no Supervisor upstream identity providers with name %q of type %q were found. "+
|
||||
"Found these upstreams: %s", specifiedIDPName, specifiedIDPType, pinnipedIDPsString)
|
||||
case specifiedIDPType != "":
|
||||
// The user specified only a type, so check if there is only one of that type found.
|
||||
discoveredName := ""
|
||||
var discoveredType idpdiscoveryv1alpha1.IDPType
|
||||
for _, idp := range pinnipedIDPs {
|
||||
if idp.Type.Equals(specifiedIDPType) {
|
||||
if discoveredName != "" {
|
||||
return "", "", fmt.Errorf(
|
||||
"multiple Supervisor upstream identity providers of type \"%s\" were found,"+
|
||||
" so the --upstream-identity-provider-name flag must be specified. "+
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"multiple Supervisor upstream identity providers of type %q were found, "+
|
||||
"so the --upstream-identity-provider-name flag must be specified. "+
|
||||
"Found these upstreams: %s",
|
||||
idpType, pinnipedIDPsString)
|
||||
specifiedIDPType, pinnipedIDPsString)
|
||||
}
|
||||
discoveredName = idp.Name
|
||||
discoveredType = idp.Type
|
||||
discoveredFlows = idp.Flows
|
||||
}
|
||||
}
|
||||
if discoveredName == "" {
|
||||
return "", "", fmt.Errorf(
|
||||
"no Supervisor upstream identity providers of type \"%s\" were found."+
|
||||
" Found these upstreams: %s", idpType, pinnipedIDPsString)
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"no Supervisor upstream identity providers of type %q were found. "+
|
||||
"Found these upstreams: %s", specifiedIDPType, pinnipedIDPsString)
|
||||
}
|
||||
return discoveredName, idpType, nil
|
||||
case idpName != "":
|
||||
discoveredType := ""
|
||||
return discoveredName, discoveredType, discoveredFlows, nil
|
||||
case specifiedIDPName != "":
|
||||
// The user specified only a name, so check if there is only one of that name found.
|
||||
var discoveredType idpdiscoveryv1alpha1.IDPType
|
||||
for _, idp := range pinnipedIDPs {
|
||||
if idp.Name == idpName {
|
||||
if idp.Name == specifiedIDPName {
|
||||
if discoveredType != "" {
|
||||
return "", "", fmt.Errorf(
|
||||
"multiple Supervisor upstream identity providers with name \"%s\" were found,"+
|
||||
" so the --upstream-identity-provider-type flag must be specified. Found these upstreams: %s",
|
||||
idpName, pinnipedIDPsString)
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"multiple Supervisor upstream identity providers with name %q were found, "+
|
||||
"so the --upstream-identity-provider-type flag must be specified. Found these upstreams: %s",
|
||||
specifiedIDPName, pinnipedIDPsString)
|
||||
}
|
||||
discoveredType = idp.Type
|
||||
discoveredFlows = idp.Flows
|
||||
}
|
||||
}
|
||||
if discoveredType == "" {
|
||||
return "", "", fmt.Errorf(
|
||||
"no Supervisor upstream identity providers with name \"%s\" were found."+
|
||||
" Found these upstreams: %s", idpName, pinnipedIDPsString)
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"no Supervisor upstream identity providers with name %q were found. "+
|
||||
"Found these upstreams: %s", specifiedIDPName, pinnipedIDPsString)
|
||||
}
|
||||
return idpName, discoveredType, nil
|
||||
return specifiedIDPName, discoveredType, discoveredFlows, nil
|
||||
case len(pinnipedIDPs) == 1:
|
||||
// The user did not specify any name or type, but there is only one found, so select it.
|
||||
return pinnipedIDPs[0].Name, pinnipedIDPs[0].Type, pinnipedIDPs[0].Flows, nil
|
||||
default:
|
||||
return "", "", fmt.Errorf(
|
||||
"multiple Supervisor upstream identity providers were found,"+
|
||||
" so the --upstream-identity-provider-name/--upstream-identity-provider-type flags must be specified."+
|
||||
" Found these upstreams: %s",
|
||||
// The user did not specify any name or type, and there is more than one found.
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"multiple Supervisor upstream identity providers were found, "+
|
||||
"so the --upstream-identity-provider-name/--upstream-identity-provider-type flags must be specified. "+
|
||||
"Found these upstreams: %s",
|
||||
pinnipedIDPsString)
|
||||
}
|
||||
}
|
||||
|
||||
func selectUpstreamIDPFlow(discoveredIDPFlows []idpdiscoveryv1alpha1.IDPFlow, selectedIDPName string, selectedIDPType idpdiscoveryv1alpha1.IDPType, specifiedFlow string) (idpdiscoveryv1alpha1.IDPFlow, error) {
|
||||
switch {
|
||||
case len(discoveredIDPFlows) == 0:
|
||||
// No flows listed by discovery means that we are talking to an old Supervisor from before this feature existed.
|
||||
// If the user specified a flow on the CLI flag then use it without validation, otherwise skip flow selection
|
||||
// and return empty string.
|
||||
return idpdiscoveryv1alpha1.IDPFlow(specifiedFlow), nil
|
||||
case specifiedFlow != "":
|
||||
// The user specified a flow, so validate that it is available for the selected IDP.
|
||||
for _, flow := range discoveredIDPFlows {
|
||||
if flow.Equals(specifiedFlow) {
|
||||
// Found it, so use it as specified by the user.
|
||||
return flow, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf(
|
||||
"no client flow %q for Supervisor upstream identity provider %q of type %q were found. "+
|
||||
"Found these flows: %v",
|
||||
specifiedFlow, selectedIDPName, selectedIDPType, discoveredIDPFlows)
|
||||
case len(discoveredIDPFlows) == 1:
|
||||
// The user did not specify a flow, but there is only one found, so select it.
|
||||
return discoveredIDPFlows[0], nil
|
||||
default:
|
||||
// The user did not specify a flow, and more than one was found.
|
||||
return "", fmt.Errorf(
|
||||
"multiple client flows for Supervisor upstream identity provider %q of type %q were found, "+
|
||||
"so the --upstream-identity-provider-flow flag must be specified. "+
|
||||
"Found these flows: %v",
|
||||
selectedIDPName, selectedIDPType, discoveredIDPFlows)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -133,6 +133,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
--credential-cache string Path to cluster-specific credentials cache
|
||||
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
|
||||
-h, --help help for kubeconfig
|
||||
--install-hint string This text is shown to the user when the pinniped CLI is not installed. (default "The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli for more details")
|
||||
--kubeconfig string Path to kubeconfig file
|
||||
--kubeconfig-context string Kubeconfig context name (default: current active context)
|
||||
--no-concierge Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly
|
||||
@@ -149,8 +150,9 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
--static-token string Instead of doing an OIDC-based login, specify a static token
|
||||
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
|
||||
--timeout duration Timeout for autodiscovery and validation (default 10m0s)
|
||||
--upstream-identity-provider-flow string The type of client flow to use with the upstream identity provider during login with a Supervisor (e.g. 'cli_password', 'browser_authcode')
|
||||
--upstream-identity-provider-name string The name of the upstream identity provider used during login with a Supervisor
|
||||
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap')
|
||||
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap', 'activedirectory')
|
||||
`)
|
||||
},
|
||||
},
|
||||
@@ -814,7 +816,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when IDP discovery document contains multiple pinniped_idps and no name or type flags are given",
|
||||
name: "when IDP discovery document contains multiple IDPs and no name or type flags are given",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
@@ -953,7 +955,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
||||
return fmt.Sprintf("Error: while fetching OIDC discovery data from issuer: Get \"%s/.well-known/openid-configuration\": x509: certificate signed by unknown authority\n", issuerURL)
|
||||
return fmt.Sprintf("Error: while fetching OIDC discovery data from issuer: Get \"%s/.well-known/openid-configuration\": %s\n", issuerURL, testutil.X509UntrustedCertError("Acme Co"))
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1033,6 +1035,33 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
return `Error: while forming request to IDP discovery URL: parse "https%://illegal_url": first path segment in URL cannot contain colon` + "\n"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery does not find matching IDP when name and type are both specified",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--no-concierge",
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
"--upstream-identity-provider-name", "does-not-exist-idp",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
}
|
||||
},
|
||||
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
||||
idpsDiscoveryResponse: here.Docf(`{
|
||||
"pinniped_identity_providers": [
|
||||
{"name": "some-ldap-idp", "type": "ldap"},
|
||||
{"name": "some-other-ldap-idp", "type": "ldap"}
|
||||
]
|
||||
}`),
|
||||
wantError: true,
|
||||
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
||||
return `Error: no Supervisor upstream identity providers with name "does-not-exist-idp" of type "ldap" were found.` +
|
||||
` Found these upstreams: [{"name":"some-ldap-idp","type":"ldap"},{"name":"some-other-ldap-idp","type":"ldap"}]` + "\n"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery fails to resolve ambiguity when type is specified but name is not",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
@@ -1091,7 +1120,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery fails to find any matching idps when type is specified but name is not",
|
||||
name: "supervisor upstream IDP discovery fails to find any matching IDPs when type is specified but name is not",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
@@ -1117,7 +1146,32 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery fails to find any matching idps when name is specified but type is not",
|
||||
name: "supervisor upstream IDP discovery fails to find any matching IDPs when type is specified but name is not and there is only one IDP found",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--no-concierge",
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
}
|
||||
},
|
||||
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
||||
idpsDiscoveryResponse: here.Docf(`{
|
||||
"pinniped_identity_providers": [
|
||||
{"name": "some-oidc-idp", "type": "oidc"}
|
||||
]
|
||||
}`),
|
||||
wantError: true,
|
||||
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
||||
return `Error: no Supervisor upstream identity providers of type "ldap" were found.` +
|
||||
` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"}]` + "\n"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery fails to find any matching IDPs when name is specified but type is not",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
@@ -1142,6 +1196,80 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery fails to find any matching IDPs when name is specified but type is not and there is only one IDP found",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--no-concierge",
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
"--upstream-identity-provider-name", "my-nonexistent-idp",
|
||||
}
|
||||
},
|
||||
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
||||
idpsDiscoveryResponse: here.Docf(`{
|
||||
"pinniped_identity_providers": [
|
||||
{"name": "some-oidc-idp", "type": "oidc"}
|
||||
]
|
||||
}`),
|
||||
wantError: true,
|
||||
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
||||
return `Error: no Supervisor upstream identity providers with name "my-nonexistent-idp" were found.` +
|
||||
` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"}]` + "\n"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery when flow is specified but it does not match any flow returned by discovery",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--no-concierge",
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
"--upstream-identity-provider-flow", "my-nonexistent-flow",
|
||||
}
|
||||
},
|
||||
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
||||
idpsDiscoveryResponse: here.Docf(`{
|
||||
"pinniped_identity_providers": [
|
||||
{"name": "some-oidc-idp", "type": "oidc", "flows": ["non-matching-flow-1", "non-matching-flow-2"]}
|
||||
]
|
||||
}`),
|
||||
wantError: true,
|
||||
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
||||
return `Error: no client flow "my-nonexistent-flow" for Supervisor upstream identity provider "some-oidc-idp" of type "oidc" were found.` +
|
||||
` Found these flows: [non-matching-flow-1 non-matching-flow-2]` + "\n"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery when no flow is specified and more than one flow is returned by discovery",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--no-concierge",
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
}
|
||||
},
|
||||
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
||||
idpsDiscoveryResponse: here.Docf(`{
|
||||
"pinniped_identity_providers": [
|
||||
{"name": "some-oidc-idp", "type": "oidc", "flows": ["flow1", "flow2"]}
|
||||
]
|
||||
}`),
|
||||
wantError: true,
|
||||
wantStderr: func(issuerCABundle string, issuerURL string) string {
|
||||
return `Error: multiple client flows for Supervisor upstream identity provider "some-oidc-idp" of type "oidc" were found, so the --upstream-identity-provider-flow flag must be specified.` +
|
||||
` Found these flows: [flow1 flow2]` + "\n"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid static token",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
@@ -1199,6 +1327,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --token=test-token
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`)
|
||||
},
|
||||
@@ -1262,6 +1392,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --token-env=TEST_TOKEN
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`)
|
||||
},
|
||||
@@ -1330,6 +1462,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --request-audience=test-audience
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -1352,6 +1486,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
"--concierge-ca-bundle", testConciergeCABundlePath,
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-skip-browser",
|
||||
"--oidc-skip-listen",
|
||||
"--oidc-listen-port", "1234",
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
"--oidc-session-cache", "/path/to/cache/dir/sessions.yaml",
|
||||
@@ -1405,6 +1540,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --client-id=pinniped-cli
|
||||
- --scopes=offline_access,openid,pinniped:request-audience
|
||||
- --skip-browser
|
||||
- --skip-listen
|
||||
- --listen-port=1234
|
||||
- --ca-bundle-data=%s
|
||||
- --session-cache=/path/to/cache/dir/sessions.yaml
|
||||
@@ -1412,6 +1548,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --request-audience=test-audience
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
|
||||
@@ -1523,6 +1661,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --request-audience=test-audience
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
base64.StdEncoding.EncodeToString(testConciergeCA.Bundle()),
|
||||
@@ -1533,7 +1673,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "autodetect impersonation proxy with autodiscovered JWT authenticator",
|
||||
name: "autodetect impersonation proxy with auto-discovered JWT authenticator",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
@@ -1630,6 +1770,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --request-audience=test-audience
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -1707,6 +1849,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --upstream-identity-provider-type=ldap
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -1784,6 +1928,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --upstream-identity-provider-type=oidc
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -1857,6 +2003,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --request-audience=test-audience
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -1928,6 +2076,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --request-audience=test-audience
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -1956,7 +2106,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
}
|
||||
}`, issuerURL)
|
||||
},
|
||||
idpsDiscoveryStatusCode: http.StatusBadRequest, // IDPs endpoint shouldn't be called by this test
|
||||
idpsDiscoveryStatusCode: http.StatusBadRequest, // IDP discovery endpoint shouldn't be called by this test
|
||||
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
||||
return []string{
|
||||
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
||||
@@ -2006,6 +2156,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --request-audience=test-audience
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -2013,13 +2165,14 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when upstream idp related flags are sent, pass them through",
|
||||
name: "when all upstream IDP related flags are sent, pass them through without performing IDP discovery",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--upstream-identity-provider-name=some-oidc-idp",
|
||||
"--upstream-identity-provider-type=oidc",
|
||||
"--upstream-identity-provider-flow=foobar",
|
||||
}
|
||||
},
|
||||
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
||||
@@ -2028,7 +2181,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
jwtAuthenticator(issuerCABundle, issuerURL),
|
||||
}
|
||||
},
|
||||
oidcDiscoveryStatusCode: http.StatusNotFound,
|
||||
oidcDiscoveryStatusCode: http.StatusNotFound, // should not get called by the client in this case
|
||||
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
||||
return []string{
|
||||
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
||||
@@ -2078,8 +2231,11 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --request-audience=test-audience
|
||||
- --upstream-identity-provider-name=some-oidc-idp
|
||||
- --upstream-identity-provider-type=oidc
|
||||
- --upstream-identity-provider-flow=foobar
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -2087,13 +2243,14 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when upstream IDP related flags are sent, pass them through even when IDP discovery shows a different IDP",
|
||||
name: "when all upstream IDP related flags are sent, pass them through even when IDP discovery shows a different IDP",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--upstream-identity-provider-name=some-oidc-idp",
|
||||
"--upstream-identity-provider-type=oidc",
|
||||
"--upstream-identity-provider-flow=foobar",
|
||||
}
|
||||
},
|
||||
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
||||
@@ -2157,8 +2314,11 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --request-audience=test-audience
|
||||
- --upstream-identity-provider-name=some-oidc-idp
|
||||
- --upstream-identity-provider-type=oidc
|
||||
- --upstream-identity-provider-flow=foobar
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -2215,6 +2375,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --upstream-identity-provider-type=ldap
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -2274,6 +2436,8 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --upstream-identity-provider-type=ldap
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
@@ -2333,11 +2497,322 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
- --upstream-identity-provider-type=ldap
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
||||
},
|
||||
}, // TODO make sure there are active directory tests for various flows
|
||||
{
|
||||
name: "supervisor upstream IDP discovery when both name and type are specified but flow is not and a matching IDP is found",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--no-concierge",
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
"--upstream-identity-provider-name", "some-ldap-idp",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
}
|
||||
},
|
||||
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
||||
idpsDiscoveryResponse: here.Docf(`{
|
||||
"pinniped_identity_providers": [
|
||||
{"name": "some-ldap-idp", "type": "ldap"},
|
||||
{"name": "some-oidc-idp", "type": "oidc"},
|
||||
{"name": "some-other-oidc-idp", "type": "oidc"}
|
||||
]
|
||||
}`),
|
||||
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
||||
return here.Docf(`
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
||||
server: https://fake-server-url-value
|
||||
name: kind-cluster-pinniped
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kind-cluster-pinniped
|
||||
user: kind-user-pinniped
|
||||
name: kind-context-pinniped
|
||||
current-context: kind-context-pinniped
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: kind-user-pinniped
|
||||
user:
|
||||
exec:
|
||||
apiVersion: client.authentication.k8s.io/v1beta1
|
||||
args:
|
||||
- login
|
||||
- oidc
|
||||
- --issuer=%s
|
||||
- --client-id=pinniped-cli
|
||||
- --scopes=offline_access,openid,pinniped:request-audience
|
||||
- --ca-bundle-data=%s
|
||||
- --upstream-identity-provider-name=some-ldap-idp
|
||||
- --upstream-identity-provider-type=ldap
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery when flow is specified and no flows were returned by discovery uses the specified flow",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--no-concierge",
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
"--upstream-identity-provider-flow", "foobar",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
}
|
||||
},
|
||||
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
||||
idpsDiscoveryResponse: here.Docf(`{
|
||||
"pinniped_identity_providers": [
|
||||
{"name": "some-ldap-idp", "type": "ldap"},
|
||||
{"name": "some-oidc-idp", "type": "oidc"},
|
||||
{"name": "some-other-oidc-idp", "type": "oidc"}
|
||||
]
|
||||
}`),
|
||||
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
||||
return here.Docf(`
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
||||
server: https://fake-server-url-value
|
||||
name: kind-cluster-pinniped
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kind-cluster-pinniped
|
||||
user: kind-user-pinniped
|
||||
name: kind-context-pinniped
|
||||
current-context: kind-context-pinniped
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: kind-user-pinniped
|
||||
user:
|
||||
exec:
|
||||
apiVersion: client.authentication.k8s.io/v1beta1
|
||||
args:
|
||||
- login
|
||||
- oidc
|
||||
- --issuer=%s
|
||||
- --client-id=pinniped-cli
|
||||
- --scopes=offline_access,openid,pinniped:request-audience
|
||||
- --ca-bundle-data=%s
|
||||
- --upstream-identity-provider-name=some-ldap-idp
|
||||
- --upstream-identity-provider-type=ldap
|
||||
- --upstream-identity-provider-flow=foobar
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery when flow is specified and it matches a flow returned by discovery uses the specified flow",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--no-concierge",
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
"--upstream-identity-provider-flow", "cli_password",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
}
|
||||
},
|
||||
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
||||
idpsDiscoveryResponse: here.Docf(`{
|
||||
"pinniped_identity_providers": [
|
||||
{"name": "some-ldap-idp", "type": "ldap", "flows": ["some_flow", "cli_password", "some_other_flow"]}
|
||||
]
|
||||
}`),
|
||||
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
||||
return here.Docf(`
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
||||
server: https://fake-server-url-value
|
||||
name: kind-cluster-pinniped
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kind-cluster-pinniped
|
||||
user: kind-user-pinniped
|
||||
name: kind-context-pinniped
|
||||
current-context: kind-context-pinniped
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: kind-user-pinniped
|
||||
user:
|
||||
exec:
|
||||
apiVersion: client.authentication.k8s.io/v1beta1
|
||||
args:
|
||||
- login
|
||||
- oidc
|
||||
- --issuer=%s
|
||||
- --client-id=pinniped-cli
|
||||
- --scopes=offline_access,openid,pinniped:request-audience
|
||||
- --ca-bundle-data=%s
|
||||
- --upstream-identity-provider-name=some-ldap-idp
|
||||
- --upstream-identity-provider-type=ldap
|
||||
- --upstream-identity-provider-flow=cli_password
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "supervisor upstream IDP discovery when no flow is specified but there is only one flow returned by discovery uses the discovered flow",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--skip-validation",
|
||||
"--no-concierge",
|
||||
"--oidc-issuer", issuerURL,
|
||||
"--oidc-ca-bundle", f.Name(),
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
}
|
||||
},
|
||||
oidcDiscoveryResponse: happyOIDCDiscoveryResponse,
|
||||
idpsDiscoveryResponse: here.Docf(`{
|
||||
"pinniped_identity_providers": [
|
||||
{"name": "some-ldap-idp", "type": "ldap", "flows": ["cli_password"]}
|
||||
]
|
||||
}`),
|
||||
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
||||
return here.Docf(`
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
||||
server: https://fake-server-url-value
|
||||
name: kind-cluster-pinniped
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kind-cluster-pinniped
|
||||
user: kind-user-pinniped
|
||||
name: kind-context-pinniped
|
||||
current-context: kind-context-pinniped
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: kind-user-pinniped
|
||||
user:
|
||||
exec:
|
||||
apiVersion: client.authentication.k8s.io/v1beta1
|
||||
args:
|
||||
- login
|
||||
- oidc
|
||||
- --issuer=%s
|
||||
- --client-id=pinniped-cli
|
||||
- --scopes=offline_access,openid,pinniped:request-audience
|
||||
- --ca-bundle-data=%s
|
||||
- --upstream-identity-provider-name=some-ldap-idp
|
||||
- --upstream-identity-provider-type=ldap
|
||||
- --upstream-identity-provider-flow=cli_password
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
|
||||
for more details
|
||||
provideClusterInfo: true
|
||||
`,
|
||||
issuerURL,
|
||||
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user specified message for install-hint flag",
|
||||
args: func(issuerCABundle string, issuerURL string) []string {
|
||||
return []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--install-hint", "Test installHint message",
|
||||
"--static-token", "test-token",
|
||||
"--skip-validation",
|
||||
}
|
||||
},
|
||||
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
|
||||
return []runtime.Object{
|
||||
credentialIssuer(),
|
||||
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
|
||||
}
|
||||
},
|
||||
wantLogs: func(issuerCABundle string, issuerURL string) []string {
|
||||
return []string{
|
||||
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
|
||||
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
|
||||
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
|
||||
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
|
||||
`"level"=0 "msg"="discovered WebhookAuthenticator" "name"="test-authenticator"`,
|
||||
}
|
||||
},
|
||||
wantStdout: func(issuerCABundle string, issuerURL string) string {
|
||||
return here.Doc(`
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
||||
server: https://fake-server-url-value
|
||||
name: kind-cluster-pinniped
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kind-cluster-pinniped
|
||||
user: kind-user-pinniped
|
||||
name: kind-context-pinniped
|
||||
current-context: kind-context-pinniped
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: kind-user-pinniped
|
||||
user:
|
||||
exec:
|
||||
apiVersion: client.authentication.k8s.io/v1beta1
|
||||
args:
|
||||
- login
|
||||
- static
|
||||
- --enable-concierge
|
||||
- --concierge-api-group-suffix=pinniped.dev
|
||||
- --concierge-authenticator-name=test-authenticator
|
||||
- --concierge-authenticator-type=webhook
|
||||
- --concierge-endpoint=https://fake-server-url-value
|
||||
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
|
||||
- --token=test-token
|
||||
command: '.../path/to/pinniped'
|
||||
env: []
|
||||
installHint: Test installHint message
|
||||
provideClusterInfo: true
|
||||
`)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -2375,7 +2850,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
})
|
||||
issuerEndpointPtr = &issuerEndpoint
|
||||
|
||||
testLog := testlogger.New(t)
|
||||
testLog := testlogger.NewLegacy(t) //nolint: staticcheck // old test with lots of log statements
|
||||
cmd := kubeconfigCommand(kubeconfigDeps{
|
||||
getPathToSelf: func() (string, error) {
|
||||
if tt.getPathToSelfErr != nil {
|
||||
@@ -2401,7 +2876,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
}
|
||||
return fake, nil
|
||||
},
|
||||
log: testLog,
|
||||
log: testLog.Logger,
|
||||
})
|
||||
require.NotNil(t, cmd)
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -14,17 +13,19 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
"k8s.io/client-go/transport"
|
||||
"k8s.io/klog/v2/klogr"
|
||||
|
||||
idpdiscoveryv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
||||
"go.pinniped.dev/internal/execcredcache"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
"go.pinniped.dev/internal/net/phttp"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/pkg/conciergeclient"
|
||||
"go.pinniped.dev/pkg/oidcclient"
|
||||
@@ -59,6 +60,7 @@ type oidcLoginFlags struct {
|
||||
listenPort uint16
|
||||
scopes []string
|
||||
skipBrowser bool
|
||||
skipListen bool
|
||||
sessionCachePath string
|
||||
caBundlePaths []string
|
||||
caBundleData []string
|
||||
@@ -73,6 +75,7 @@ type oidcLoginFlags struct {
|
||||
credentialCachePath string
|
||||
upstreamIdentityProviderName string
|
||||
upstreamIdentityProviderType string
|
||||
upstreamIdentityProviderFlow string
|
||||
}
|
||||
|
||||
func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
||||
@@ -91,6 +94,7 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
||||
cmd.Flags().Uint16Var(&flags.listenPort, "listen-port", 0, "TCP port for localhost listener (authorization code flow only)")
|
||||
cmd.Flags().StringSliceVar(&flags.scopes, "scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped:request-audience"}, "OIDC scopes to request during login")
|
||||
cmd.Flags().BoolVar(&flags.skipBrowser, "skip-browser", false, "Skip opening the browser (just print the URL)")
|
||||
cmd.Flags().BoolVar(&flags.skipListen, "skip-listen", false, "Skip starting a localhost callback listener (manual copy/paste flow only)")
|
||||
cmd.Flags().StringVar(&flags.sessionCachePath, "session-cache", filepath.Join(mustGetConfigDir(), "sessions.yaml"), "Path to session cache file")
|
||||
cmd.Flags().StringSliceVar(&flags.caBundlePaths, "ca-bundle", nil, "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)")
|
||||
cmd.Flags().StringSliceVar(&flags.caBundleData, "ca-bundle-data", nil, "Base64 encoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)")
|
||||
@@ -105,8 +109,11 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
||||
cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix")
|
||||
cmd.Flags().StringVar(&flags.credentialCachePath, "credential-cache", filepath.Join(mustGetConfigDir(), "credentials.yaml"), "Path to cluster-specific credentials cache (\"\" disables the cache)")
|
||||
cmd.Flags().StringVar(&flags.upstreamIdentityProviderName, "upstream-identity-provider-name", "", "The name of the upstream identity provider used during login with a Supervisor")
|
||||
cmd.Flags().StringVar(&flags.upstreamIdentityProviderType, "upstream-identity-provider-type", "oidc", "The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap')")
|
||||
cmd.Flags().StringVar(&flags.upstreamIdentityProviderType, "upstream-identity-provider-type", idpdiscoveryv1alpha1.IDPTypeOIDC.String(), fmt.Sprintf("The type of the upstream identity provider used during login with a Supervisor (e.g. '%s', '%s', '%s')", idpdiscoveryv1alpha1.IDPTypeOIDC, idpdiscoveryv1alpha1.IDPTypeLDAP, idpdiscoveryv1alpha1.IDPTypeActiveDirectory))
|
||||
cmd.Flags().StringVar(&flags.upstreamIdentityProviderFlow, "upstream-identity-provider-flow", "", fmt.Sprintf("The type of client flow to use with the upstream identity provider during login with a Supervisor (e.g. '%s', '%s')", idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode, idpdiscoveryv1alpha1.IDPFlowCLIPassword))
|
||||
|
||||
// --skip-listen is mainly needed for testing. We'll leave it hidden until we have a non-testing use case.
|
||||
mustMarkHidden(cmd, "skip-listen")
|
||||
mustMarkHidden(cmd, "debug-session-cache")
|
||||
mustMarkRequired(cmd, "issuer")
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error { return runOIDCLogin(cmd, deps, flags) }
|
||||
@@ -156,17 +163,14 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
flags.upstreamIdentityProviderName, flags.upstreamIdentityProviderType))
|
||||
}
|
||||
|
||||
switch flags.upstreamIdentityProviderType {
|
||||
case "oidc":
|
||||
// this is the default, so don't need to do anything
|
||||
case "ldap":
|
||||
opts = append(opts, oidcclient.WithCLISendingCredentials())
|
||||
default:
|
||||
// Surprisingly cobra does not support this kind of flag validation. See https://github.com/spf13/pflag/issues/236
|
||||
return fmt.Errorf(
|
||||
"--upstream-identity-provider-type value not recognized: %s (supported values: oidc, ldap)",
|
||||
flags.upstreamIdentityProviderType)
|
||||
flowOpts, err := flowOptions(
|
||||
idpdiscoveryv1alpha1.IDPType(flags.upstreamIdentityProviderType),
|
||||
idpdiscoveryv1alpha1.IDPFlow(flags.upstreamIdentityProviderFlow),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, flowOpts...)
|
||||
|
||||
var concierge *conciergeclient.Client
|
||||
if flags.conciergeEnabled {
|
||||
@@ -182,12 +186,14 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
}
|
||||
}
|
||||
|
||||
// --skip-browser replaces the default "browser open" function with one that prints to stderr.
|
||||
// --skip-browser skips opening the browser.
|
||||
if flags.skipBrowser {
|
||||
opts = append(opts, oidcclient.WithBrowserOpen(func(url string) error {
|
||||
cmd.PrintErr("Please log in: ", url, "\n")
|
||||
return nil
|
||||
}))
|
||||
opts = append(opts, oidcclient.WithSkipBrowserOpen())
|
||||
}
|
||||
|
||||
// --skip-listen skips starting the localhost callback listener.
|
||||
if flags.skipListen {
|
||||
opts = append(opts, oidcclient.WithSkipListen())
|
||||
}
|
||||
|
||||
if len(flags.caBundlePaths) > 0 || len(flags.caBundleData) > 0 {
|
||||
@@ -245,6 +251,46 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
return json.NewEncoder(cmd.OutOrStdout()).Encode(cred)
|
||||
}
|
||||
|
||||
func flowOptions(requestedIDPType idpdiscoveryv1alpha1.IDPType, requestedFlow idpdiscoveryv1alpha1.IDPFlow) ([]oidcclient.Option, error) {
|
||||
useCLIFlow := []oidcclient.Option{oidcclient.WithCLISendingCredentials()}
|
||||
|
||||
switch requestedIDPType {
|
||||
case idpdiscoveryv1alpha1.IDPTypeOIDC:
|
||||
switch requestedFlow {
|
||||
case idpdiscoveryv1alpha1.IDPFlowCLIPassword:
|
||||
return useCLIFlow, nil
|
||||
case idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode, "":
|
||||
return nil, nil // browser authcode flow is the default Option, so don't need to return an Option here
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"--upstream-identity-provider-flow value not recognized for identity provider type %q: %s (supported values: %s)",
|
||||
requestedIDPType, requestedFlow, strings.Join([]string{idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode.String(), idpdiscoveryv1alpha1.IDPFlowCLIPassword.String()}, ", "))
|
||||
}
|
||||
case idpdiscoveryv1alpha1.IDPTypeLDAP, idpdiscoveryv1alpha1.IDPTypeActiveDirectory:
|
||||
switch requestedFlow {
|
||||
case idpdiscoveryv1alpha1.IDPFlowCLIPassword, "":
|
||||
return useCLIFlow, nil
|
||||
case idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode:
|
||||
fallthrough // not supported for LDAP providers, so fallthrough to error case
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"--upstream-identity-provider-flow value not recognized for identity provider type %q: %s (supported values: %s)",
|
||||
requestedIDPType, requestedFlow, []string{idpdiscoveryv1alpha1.IDPFlowCLIPassword.String()})
|
||||
}
|
||||
default:
|
||||
// Surprisingly cobra does not support this kind of flag validation. See https://github.com/spf13/pflag/issues/236
|
||||
return nil, fmt.Errorf(
|
||||
"--upstream-identity-provider-type value not recognized: %s (supported values: %s)",
|
||||
requestedIDPType,
|
||||
strings.Join([]string{
|
||||
idpdiscoveryv1alpha1.IDPTypeOIDC.String(),
|
||||
idpdiscoveryv1alpha1.IDPTypeLDAP.String(),
|
||||
idpdiscoveryv1alpha1.IDPTypeActiveDirectory.String(),
|
||||
}, ", "),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, error) {
|
||||
pool := x509.NewCertPool()
|
||||
for _, p := range caBundlePaths {
|
||||
@@ -261,18 +307,7 @@ func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, er
|
||||
}
|
||||
pool.AppendCertsFromPEM(pem)
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: pool,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
client.Transport = transport.DebugWrappers(client.Transport)
|
||||
return client, nil
|
||||
return phttp.Default(pool), nil
|
||||
}
|
||||
|
||||
func tokenCredential(token *oidctypes.Token) *clientauthv1beta1.ExecCredential {
|
||||
@@ -291,7 +326,7 @@ func tokenCredential(token *oidctypes.Token) *clientauthv1beta1.ExecCredential {
|
||||
return &cred
|
||||
}
|
||||
|
||||
func SetLogLevel(lookupEnv func(string) (string, bool)) (*plog.PLogger, error) {
|
||||
func SetLogLevel(lookupEnv func(string) (string, bool)) (plog.Logger, error) {
|
||||
debug, _ := lookupEnv("PINNIPED_DEBUG")
|
||||
if debug == "true" {
|
||||
err := plog.ValidateAndSetLogLevelGlobally(plog.LevelDebug)
|
||||
@@ -300,7 +335,7 @@ func SetLogLevel(lookupEnv func(string) (string, bool)) (*plog.PLogger, error) {
|
||||
}
|
||||
}
|
||||
logger := plog.New("Pinniped login: ")
|
||||
return &logger, nil
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
// mustGetConfigDir returns a directory that follows the XDG base directory convention:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -77,8 +77,9 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
--scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped:request-audience])
|
||||
--session-cache string Path to session cache file (default "` + cfgDir + `/sessions.yaml")
|
||||
--skip-browser Skip opening the browser (just print the URL)
|
||||
--upstream-identity-provider-flow string The type of client flow to use with the upstream identity provider during login with a Supervisor (e.g. 'browser_authcode', 'cli_password')
|
||||
--upstream-identity-provider-name string The name of the upstream identity provider used during login with a Supervisor
|
||||
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap') (default "oidc")
|
||||
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap', 'activedirectory') (default "oidc")
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -148,11 +149,11 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: --upstream-identity-provider-type value not recognized: invalid (supported values: oidc, ldap)
|
||||
Error: --upstream-identity-provider-type value not recognized: invalid (supported values: oidc, ldap, activedirectory)
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "oidc upstream type is allowed",
|
||||
name: "oidc upstream type with default flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
@@ -160,10 +161,48 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 4,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "ldap upstream type is allowed",
|
||||
name: "oidc upstream type with CLI flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "oidc",
|
||||
"--upstream-identity-provider-flow", "cli_password",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 5,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "oidc upstream type with browser flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "oidc",
|
||||
"--upstream-identity-provider-flow", "browser_authcode",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 4,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "oidc upstream type with unsupported flow is an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "oidc",
|
||||
"--upstream-identity-provider-flow", "foobar",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: --upstream-identity-provider-flow value not recognized for identity provider type "oidc": foobar (supported values: browser_authcode, cli_password)
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "ldap upstream type with default flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
@@ -171,7 +210,70 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 5,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "activedirectory upstream type with default flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 5,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "ldap upstream type with CLI flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--upstream-identity-provider-flow", "cli_password",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 5,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "ldap upstream type with unsupported flow is an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--upstream-identity-provider-flow", "browser_authcode", // "browser_authcode" is only supported for OIDC upstreams
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: --upstream-identity-provider-flow value not recognized for identity provider type "ldap": browser_authcode (supported values: [cli_password])
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "active directory upstream type with CLI flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--upstream-identity-provider-flow", "cli_password",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 5,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "active directory upstream type with unsupported flow is an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--upstream-identity-provider-flow", "browser_authcode", // "browser_authcode" is only supported for OIDC upstreams
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: --upstream-identity-provider-flow value not recognized for identity provider type "activedirectory": browser_authcode (supported values: [cli_password])
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "login error",
|
||||
@@ -214,7 +316,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
},
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantOptionsCount: 4,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
wantLogs: []string{
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: Performing OIDC login\" \"client id\"=\"test-client-id\" \"issuer\"=\"test-issuer\"",
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: No concierge configured, skipping token credential exchange\"",
|
||||
@@ -226,6 +328,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--client-id", "test-client-id",
|
||||
"--issuer", "test-issuer",
|
||||
"--skip-browser",
|
||||
"--skip-listen",
|
||||
"--listen-port", "1234",
|
||||
"--debug-session-cache",
|
||||
"--request-audience", "cluster-1234",
|
||||
@@ -242,8 +345,8 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
},
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantOptionsCount: 10,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"exchanged-token"}}` + "\n",
|
||||
wantOptionsCount: 11,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"exchanged-token"}}` + "\n",
|
||||
wantLogs: []string{
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: Performing OIDC login\" \"client id\"=\"test-client-id\" \"issuer\"=\"test-issuer\"",
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: Exchanging token for cluster credential\" \"authenticator name\"=\"test-authenticator\" \"authenticator type\"=\"webhook\" \"endpoint\"=\"https://127.0.0.1:1234/\"",
|
||||
@@ -255,8 +358,8 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testLogger := testlogger.New(t)
|
||||
klog.SetLogger(testLogger)
|
||||
testLogger := testlogger.NewLegacy(t) //nolint: staticcheck // old test with lots of log statements
|
||||
klog.SetLogger(testLogger.Logger)
|
||||
var (
|
||||
gotOptions []oidcclient.Option
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -119,7 +119,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"TEST_TOKEN_ENV": "test-token",
|
||||
},
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"test-token"}}` + "\n",
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"test-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "concierge failure",
|
||||
@@ -159,14 +159,14 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
"--token", "test-token",
|
||||
},
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"test-token"}}` + "\n",
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"test-token"}}` + "\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testLogger := testlogger.New(t)
|
||||
klog.SetLogger(testLogger)
|
||||
testLogger := testlogger.NewLegacy(t) //nolint: staticcheck // old test with lots of log statements
|
||||
klog.SetLogger(testLogger.Logger)
|
||||
cmd := staticLoginCommand(staticLoginDeps{
|
||||
lookupEnv: func(s string) (string, bool) {
|
||||
v, ok := tt.env[s]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
@@ -19,12 +17,6 @@ var rootCmd = &cobra.Command{
|
||||
SilenceUsage: true, // do not print usage message when commands fail
|
||||
}
|
||||
|
||||
//nolint: gochecknoinits
|
||||
func init() {
|
||||
// We don't want klog flags showing up in our CLI.
|
||||
plog.RemoveKlogGlobalFlags()
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import "go.pinniped.dev/cmd/pinniped/cmd"
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/browser"
|
||||
|
||||
"go.pinniped.dev/cmd/pinniped/cmd"
|
||||
// this side effect import ensures that we use fipsonly crypto in fips_strict mode.
|
||||
_ "go.pinniped.dev/internal/crypto/ptls"
|
||||
)
|
||||
|
||||
//nolint: gochecknoinits
|
||||
func init() {
|
||||
// browsers like chrome like to write to our std out which breaks our JSON ExecCredential output
|
||||
// thus we redirect the browser's std out to our std err
|
||||
browser.Stdout = os.Stderr
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.4.0
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
name: jwtauthenticators.authentication.concierge.pinniped.dev
|
||||
spec:
|
||||
@@ -24,6 +23,12 @@ spec:
|
||||
- jsonPath: .spec.issuer
|
||||
name: Issuer
|
||||
type: string
|
||||
- jsonPath: .spec.audience
|
||||
name: Audience
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.4.0
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
name: webhookauthenticators.authentication.concierge.pinniped.dev
|
||||
spec:
|
||||
@@ -24,6 +23,9 @@ spec:
|
||||
- jsonPath: .spec.endpoint
|
||||
name: Endpoint
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.4.0
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
name: credentialissuers.config.concierge.pinniped.dev
|
||||
spec:
|
||||
@@ -18,7 +17,17 @@ spec:
|
||||
singular: credentialissuer
|
||||
scope: Cluster
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.impersonationProxy.mode
|
||||
name: ProxyMode
|
||||
type: string
|
||||
- jsonPath: .status.strategies[?(@.status == "Success")].type
|
||||
name: DefaultStrategy
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: CredentialIssuer describes the configuration and status of the
|
||||
@@ -47,7 +56,7 @@ spec:
|
||||
description: "ExternalEndpoint describes the HTTPS endpoint where
|
||||
the proxy will be exposed. If not set, the proxy will be served
|
||||
using the external name of the LoadBalancer service or the cluster
|
||||
service DNS name. \n This field must be non-empty when spec.impersonationProxy.service.mode
|
||||
service DNS name. \n This field must be non-empty when spec.impersonationProxy.service.type
|
||||
is \"None\"."
|
||||
type: string
|
||||
mode:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("@ytt:json", "json")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel", "pinnipedDevAPIGroupWithPrefix")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "deploymentPodLabel", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel", "pinnipedDevAPIGroupWithPrefix")
|
||||
#@ load("@ytt:template", "template")
|
||||
|
||||
#@ if not data.values.into_namespace:
|
||||
---
|
||||
@@ -57,6 +58,8 @@ data:
|
||||
durationSeconds: (@= str(data.values.api_serving_certificate_duration_seconds) @)
|
||||
renewBeforeSeconds: (@= str(data.values.api_serving_certificate_renew_before_seconds) @)
|
||||
apiGroupSuffix: (@= data.values.api_group_suffix @)
|
||||
# aggregatedAPIServerPort may be set here, although other YAML references to the default port (10250) may also need to be updated
|
||||
# impersonationProxyServerPort may be set here, although other YAML references to the default port (8444) may also need to be updated
|
||||
names:
|
||||
servingCertificateSecret: (@= defaultResourceNameWithSuffix("api-tls-serving-certificate") @)
|
||||
credentialIssuer: (@= defaultResourceNameWithSuffix("config") @)
|
||||
@@ -108,10 +111,16 @@ metadata:
|
||||
spec:
|
||||
replicas: #@ data.values.replicas
|
||||
selector:
|
||||
#! In hindsight, this should have been deploymentPodLabel(), but this field is immutable so changing it would break upgrades.
|
||||
matchLabels: #@ defaultLabel()
|
||||
template:
|
||||
metadata:
|
||||
labels: #@ defaultLabel()
|
||||
labels:
|
||||
#! This has always included defaultLabel(), which is used by this Deployment's selector.
|
||||
_: #@ template.replace(defaultLabel())
|
||||
#! More recently added the more unique deploymentPodLabel() so Services can select these Pods more specifically
|
||||
#! without accidentally selecting any other Deployment's Pods, especially the kube cert agent Deployment's Pods.
|
||||
_: #@ template.replace(deploymentPodLabel())
|
||||
annotations:
|
||||
scheduler.alpha.kubernetes.io/critical-pod: ""
|
||||
spec:
|
||||
@@ -131,6 +140,8 @@ spec:
|
||||
image: #@ data.values.image_repo + ":" + data.values.image_tag
|
||||
#@ end
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
@@ -138,20 +149,35 @@ spec:
|
||||
limits:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
args:
|
||||
command:
|
||||
- pinniped-concierge
|
||||
- --config=/etc/config/pinniped.yaml
|
||||
- --downward-api-path=/etc/podinfo
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: config-volume
|
||||
mountPath: /etc/config
|
||||
readOnly: true
|
||||
- name: podinfo
|
||||
mountPath: /etc/podinfo
|
||||
readOnly: true
|
||||
- name: impersonation-proxy
|
||||
mountPath: /var/run/secrets/impersonation-proxy.concierge.pinniped.dev/serviceaccount
|
||||
readOnly: true
|
||||
env:
|
||||
#@ if data.values.https_proxy:
|
||||
- name: HTTPS_PROXY
|
||||
value: #@ data.values.https_proxy
|
||||
#@ end
|
||||
#@ if data.values.https_proxy and data.values.no_proxy:
|
||||
- name: NO_PROXY
|
||||
value: #@ data.values.no_proxy
|
||||
#@ end
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8443
|
||||
port: 10250
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 2
|
||||
timeoutSeconds: 15
|
||||
@@ -160,13 +186,17 @@ spec:
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8443
|
||||
port: 10250
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 2
|
||||
timeoutSeconds: 3
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 100Mi
|
||||
- name: config-volume
|
||||
configMap:
|
||||
name: #@ defaultResourceNameWithSuffix("config")
|
||||
@@ -191,7 +221,9 @@ spec:
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/master #! Allow running on master nodes too
|
||||
- key: node-role.kubernetes.io/master #! Allow running on master nodes too (name deprecated by kubernetes 1.20).
|
||||
effect: NoSchedule
|
||||
- key: node-role.kubernetes.io/control-plane #! The new name for these nodes as of Kubernetes 1.24.
|
||||
effect: NoSchedule
|
||||
#! "system-cluster-critical" cannot be used outside the kube-system namespace until Kubernetes >= 1.17,
|
||||
#! so we skip setting this for now (see https://github.com/kubernetes/kubernetes/issues/60596).
|
||||
@@ -204,7 +236,7 @@ spec:
|
||||
- weight: 50
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels: #@ defaultLabel()
|
||||
matchLabels: #@ deploymentPodLabel()
|
||||
topologyKey: kubernetes.io/hostname
|
||||
---
|
||||
apiVersion: v1
|
||||
@@ -214,13 +246,16 @@ metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("api")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector: #@ defaultLabel()
|
||||
selector: #@ deploymentPodLabel()
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
targetPort: 8443
|
||||
targetPort: 10250
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -228,9 +263,12 @@ metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("proxy")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector: #@ defaultLabel()
|
||||
selector: #@ deploymentPodLabel()
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
|
||||
@@ -25,9 +25,14 @@
|
||||
#@ end
|
||||
|
||||
#@ def defaultLabel():
|
||||
#! Note that the name of this label's key is also assumed by kubecertagent.go and impersonator_config.go
|
||||
app: #@ data.values.app_name
|
||||
#@ end
|
||||
|
||||
#@ def deploymentPodLabel():
|
||||
deployment.pinniped.dev: concierge
|
||||
#@ end
|
||||
|
||||
#@ def labels():
|
||||
_: #@ template.replace(defaultLabel())
|
||||
_: #@ template.replace(data.values.custom_labels)
|
||||
|
||||
@@ -145,7 +145,7 @@ rules:
|
||||
#! We need to be able to create and update deployments in our namespace so we can manage the kube-cert-agent Deployment.
|
||||
- apiGroups: [ apps ]
|
||||
resources: [ deployments ]
|
||||
verbs: [ create, get, list, patch, update, watch ]
|
||||
verbs: [ create, get, list, patch, update, watch, delete ]
|
||||
#! We need to be able to get replicasets so we can form the correct owner references on our generated objects.
|
||||
- apiGroups: [ apps ]
|
||||
resources: [ replicasets ]
|
||||
@@ -153,6 +153,9 @@ rules:
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ configmaps ]
|
||||
verbs: [ list, get, watch ]
|
||||
- apiGroups: [ coordination.k8s.io ]
|
||||
resources: [ leases ]
|
||||
verbs: [ create, get, update ]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
||||
@@ -41,7 +41,7 @@ kube_cert_agent_image:
|
||||
image_pull_dockerconfigjson: #! e.g. {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}
|
||||
|
||||
#! Pinniped will try to guess the right K8s API URL for sharing that information with potential clients.
|
||||
#! This settings allows the guess to be overridden.
|
||||
#! This setting allows the guess to be overridden.
|
||||
#! Optional.
|
||||
discovery_url: #! e.g., https://example.com
|
||||
|
||||
@@ -55,8 +55,8 @@ api_serving_certificate_renew_before_seconds: 2160000
|
||||
#! information), trace (timing information), all (kitchen sink).
|
||||
log_level: #! By default, when this value is left unset, only warnings and errors are printed. There is no way to suppress warning and error logs.
|
||||
|
||||
run_as_user: 1001 #! run_as_user specifies the user ID that will own the process
|
||||
run_as_group: 1001 #! run_as_group specifies the group ID that will own the process
|
||||
run_as_user: 65532 #! run_as_user specifies the user ID that will own the process, see the Dockerfile for the reasoning behind this choice
|
||||
run_as_group: 65532 #! run_as_group specifies the group ID that will own the process, see the Dockerfile for the reasoning behind this choice
|
||||
|
||||
#! Specify the API group suffix for all Pinniped API groups. By default, this is set to
|
||||
#! pinniped.dev, so Pinniped API groups will look like foo.pinniped.dev,
|
||||
@@ -93,3 +93,11 @@ impersonation_proxy_spec:
|
||||
{service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "4000"}
|
||||
#! When mode LoadBalancer is set, this will set the LoadBalancer Service's Spec.LoadBalancerIP.
|
||||
load_balancer_ip:
|
||||
|
||||
#! Set the standard golang HTTPS_PROXY and NO_PROXY environment variables on the Concierge containers.
|
||||
#! These will be used when the Concierge makes backend-to-backend calls to authenticators using HTTPS,
|
||||
#! e.g. when the Concierge fetches discovery documents, JWKS keys, and POSTs to token webhooks.
|
||||
#! The Concierge never makes insecure HTTP calls, so there is no reason to set HTTP_PROXY.
|
||||
#! Optional.
|
||||
https_proxy: #! e.g. http://proxy.example.com
|
||||
no_proxy: "$(KUBERNETES_SERVICE_HOST),169.254.169.254,127.0.0.1,localhost,.svc,.cluster.local" #! do not proxy Kubernetes endpoints
|
||||
|
||||
@@ -63,8 +63,8 @@ spec:
|
||||
image: #@ data.values.image_repo + ":" + data.values.image_tag
|
||||
#@ end
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: #! override the default entrypoint
|
||||
- /usr/local/bin/local-user-authenticator
|
||||
command:
|
||||
- local-user-authenticator
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -73,6 +73,9 @@ metadata:
|
||||
namespace: local-user-authenticator
|
||||
labels:
|
||||
app: local-user-authenticator
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
|
||||
@@ -15,5 +15,5 @@ image_tag: latest
|
||||
#! Optional.
|
||||
image_pull_dockerconfigjson: #! e.g. {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}
|
||||
|
||||
run_as_user: 1001 #! run_as_user specifies the user ID that will own the process
|
||||
run_as_group: 1001 #! run_as_group specifies the group ID that will own the process
|
||||
run_as_user: 65532 #! run_as_user specifies the user ID that will own the process, see the Dockerfile for the reasoning behind this choice
|
||||
run_as_group: 65532 #! run_as_group specifies the group ID that will own the process, see the Dockerfile for the reasoning behind this choice
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.4.0
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
name: federationdomains.config.supervisor.pinniped.dev
|
||||
spec:
|
||||
@@ -18,7 +17,17 @@ spec:
|
||||
singular: federationdomain
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.issuer
|
||||
name: Issuer
|
||||
type: string
|
||||
- jsonPath: .status.status
|
||||
name: Status
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: FederationDomain describes the configuration of an OIDC provider.
|
||||
@@ -67,12 +76,13 @@ spec:
|
||||
so all issuers with the same DNS hostname must use the same
|
||||
SecretName value even if they have different port numbers. \n
|
||||
SecretName is not required when you would like to use only the
|
||||
HTTP endpoints (e.g. when terminating TLS at an Ingress). It
|
||||
is also not required when you would like all requests to this
|
||||
OIDC Provider's HTTPS endpoints to use the default TLS certificate,
|
||||
which is configured elsewhere. \n When your Issuer URL's host
|
||||
is an IP address, then this field is ignored. SNI does not work
|
||||
for IP addresses."
|
||||
HTTP endpoints (e.g. when the HTTP listener is configured to
|
||||
listen on loopback interfaces or UNIX domain sockets for traffic
|
||||
from a service mesh sidecar). It is also not required when you
|
||||
would like all requests to this OIDC Provider's HTTPS endpoints
|
||||
to use the default TLS certificate, which is configured elsewhere.
|
||||
\n When your Issuer URL's host is an IP address, then this field
|
||||
is ignored. SNI does not work for IP addresses."
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("@ytt:json", "json")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel")
|
||||
#@ load("@ytt:yaml", "yaml")
|
||||
#@ load("helpers.lib.yaml",
|
||||
#@ "defaultLabel",
|
||||
#@ "labels",
|
||||
#@ "deploymentPodLabel",
|
||||
#@ "namespace",
|
||||
#@ "defaultResourceName",
|
||||
#@ "defaultResourceNameWithSuffix",
|
||||
#@ "getPinnipedConfigMapData",
|
||||
#@ "hasUnixNetworkEndpoint",
|
||||
#@ )
|
||||
#@ load("@ytt:template", "template")
|
||||
|
||||
#@ if not data.values.into_namespace:
|
||||
---
|
||||
@@ -29,14 +39,7 @@ metadata:
|
||||
labels: #@ labels()
|
||||
data:
|
||||
#@yaml/text-templated-strings
|
||||
pinniped.yaml: |
|
||||
apiGroupSuffix: (@= data.values.api_group_suffix @)
|
||||
names:
|
||||
defaultTLSCertificateSecret: (@= defaultResourceNameWithSuffix("default-tls-certificate") @)
|
||||
labels: (@= json.encode(labels()).rstrip() @)
|
||||
(@ if data.values.log_level: @)
|
||||
logLevel: (@= getAndValidateLogLevel() @)
|
||||
(@ end @)
|
||||
pinniped.yaml: #@ yaml.encode(getPinnipedConfigMapData())
|
||||
---
|
||||
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
|
||||
apiVersion: v1
|
||||
@@ -59,10 +62,16 @@ metadata:
|
||||
spec:
|
||||
replicas: #@ data.values.replicas
|
||||
selector:
|
||||
#! In hindsight, this should have been deploymentPodLabel(), but this field is immutable so changing it would break upgrades.
|
||||
matchLabels: #@ defaultLabel()
|
||||
template:
|
||||
metadata:
|
||||
labels: #@ defaultLabel()
|
||||
labels:
|
||||
#! This has always included defaultLabel(), which is used by this Deployment's selector.
|
||||
_: #@ template.replace(defaultLabel())
|
||||
#! More recently added the more unique deploymentPodLabel() so Services can select these Pods more specifically
|
||||
#! without accidentally selecting pods from any future Deployments which might also want to use the defaultLabel().
|
||||
_: #@ template.replace(deploymentPodLabel())
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: #@ data.values.run_as_user
|
||||
@@ -80,11 +89,12 @@ spec:
|
||||
image: #@ data.values.image_repo + ":" + data.values.image_tag
|
||||
#@ end
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: #! override the default entrypoint
|
||||
- /usr/local/bin/pinniped-supervisor
|
||||
args:
|
||||
command:
|
||||
- pinniped-supervisor
|
||||
- /etc/podinfo
|
||||
- /etc/config/pinniped.yaml
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
@@ -95,18 +105,32 @@ spec:
|
||||
volumeMounts:
|
||||
- name: config-volume
|
||||
mountPath: /etc/config
|
||||
readOnly: true
|
||||
- name: podinfo
|
||||
mountPath: /etc/podinfo
|
||||
readOnly: true
|
||||
#@ if hasUnixNetworkEndpoint():
|
||||
- name: socket
|
||||
mountPath: /pinniped_socket
|
||||
readOnly: false #! writable to allow for socket use
|
||||
#@ end
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
- containerPort: 8443
|
||||
protocol: TCP
|
||||
env:
|
||||
#@ if data.values.https_proxy:
|
||||
- name: HTTPS_PROXY
|
||||
value: #@ data.values.https_proxy
|
||||
#@ end
|
||||
#@ if data.values.https_proxy and data.values.no_proxy:
|
||||
- name: NO_PROXY
|
||||
value: #@ data.values.no_proxy
|
||||
#@ end
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
scheme: HTTP
|
||||
port: 8443
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 2
|
||||
timeoutSeconds: 15
|
||||
periodSeconds: 10
|
||||
@@ -114,8 +138,8 @@ spec:
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
scheme: HTTP
|
||||
port: 8443
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 2
|
||||
timeoutSeconds: 3
|
||||
periodSeconds: 10
|
||||
@@ -136,6 +160,10 @@ spec:
|
||||
- path: "name"
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
#@ if hasUnixNetworkEndpoint():
|
||||
- name: socket
|
||||
emptyDir: {}
|
||||
#@ end
|
||||
#! This will help make sure our multiple pods run on different nodes, making
|
||||
#! our deployment "more" "HA".
|
||||
affinity:
|
||||
@@ -144,5 +172,5 @@ spec:
|
||||
- weight: 50
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels: #@ defaultLabel()
|
||||
matchLabels: #@ deploymentPodLabel()
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
@@ -28,6 +28,10 @@
|
||||
app: #@ data.values.app_name
|
||||
#@ end
|
||||
|
||||
#@ def deploymentPodLabel():
|
||||
deployment.pinniped.dev: supervisor
|
||||
#@ end
|
||||
|
||||
#@ def labels():
|
||||
_: #@ template.replace(defaultLabel())
|
||||
_: #@ template.replace(data.values.custom_labels)
|
||||
@@ -40,3 +44,38 @@ _: #@ template.replace(data.values.custom_labels)
|
||||
#@ end
|
||||
#@ return log_level
|
||||
#@ end
|
||||
|
||||
#@ def getPinnipedConfigMapData():
|
||||
#@ config = {
|
||||
#@ "apiGroupSuffix": data.values.api_group_suffix,
|
||||
#@ "names": {
|
||||
#@ "defaultTLSCertificateSecret": defaultResourceNameWithSuffix("default-tls-certificate"),
|
||||
#@ },
|
||||
#@ "labels": labels(),
|
||||
#@ "insecureAcceptExternalUnencryptedHttpRequests": data.values.deprecated_insecure_accept_external_unencrypted_http_requests
|
||||
#@ }
|
||||
#@ if data.values.log_level:
|
||||
#@ config["logLevel"] = getAndValidateLogLevel()
|
||||
#@ end
|
||||
#@ if data.values.endpoints:
|
||||
#@ config["endpoints"] = data.values.endpoints
|
||||
#@ end
|
||||
#@ return config
|
||||
#@ end
|
||||
|
||||
#@ def getattr_safe(val, *args):
|
||||
#@ out = None
|
||||
#@ for arg in args:
|
||||
#@ if not hasattr(val, arg):
|
||||
#@ return None
|
||||
#@ end
|
||||
#@ out = getattr(val, arg)
|
||||
#@ val = out
|
||||
#@ end
|
||||
#@ return out
|
||||
#@ end
|
||||
|
||||
#@ def hasUnixNetworkEndpoint():
|
||||
#@ return getattr_safe(data.values.endpoints, "http", "network") == "unix" or \
|
||||
#@ getattr_safe(data.values.endpoints, "https", "network") == "unix"
|
||||
#@ end
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
name: activedirectoryidentityproviders.idp.supervisor.pinniped.dev
|
||||
spec:
|
||||
group: idp.supervisor.pinniped.dev
|
||||
names:
|
||||
categories:
|
||||
- pinniped
|
||||
- pinniped-idp
|
||||
- pinniped-idps
|
||||
kind: ActiveDirectoryIdentityProvider
|
||||
listKind: ActiveDirectoryIdentityProviderList
|
||||
plural: activedirectoryidentityproviders
|
||||
singular: activedirectoryidentityprovider
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.host
|
||||
name: Host
|
||||
type: string
|
||||
- jsonPath: .status.phase
|
||||
name: Status
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ActiveDirectoryIdentityProvider describes the configuration of
|
||||
an upstream Microsoft Active Directory identity provider.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: Spec for configuring the identity provider.
|
||||
properties:
|
||||
bind:
|
||||
description: Bind contains the configuration for how to provide access
|
||||
credentials during an initial bind to the ActiveDirectory server
|
||||
to be allowed to perform searches and binds to validate a user's
|
||||
credentials during a user's authentication attempt.
|
||||
properties:
|
||||
secretName:
|
||||
description: SecretName contains the name of a namespace-local
|
||||
Secret object that provides the username and password for an
|
||||
Active Directory bind user. This account will be used to perform
|
||||
LDAP searches. The Secret should be of type "kubernetes.io/basic-auth"
|
||||
which includes "username" and "password" keys. The username
|
||||
value should be the full dn (distinguished name) of your bind
|
||||
account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
|
||||
The password must be non-empty.
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
type: object
|
||||
groupSearch:
|
||||
description: GroupSearch contains the configuration for searching
|
||||
for a user's group membership in ActiveDirectory.
|
||||
properties:
|
||||
attributes:
|
||||
description: Attributes specifies how the group's information
|
||||
should be read from each ActiveDirectory entry which was found
|
||||
as the result of the group search.
|
||||
properties:
|
||||
groupName:
|
||||
description: GroupName specifies the name of the attribute
|
||||
in the Active Directory entries whose value shall become
|
||||
a group name in the user's list of groups after a successful
|
||||
authentication. The value of this field is case-sensitive
|
||||
and must match the case of the attribute name returned by
|
||||
the ActiveDirectory server in the user's entry. E.g. "cn"
|
||||
for common name. Distinguished names can be used by specifying
|
||||
lower-case "dn". Optional. When not specified, this defaults
|
||||
to a custom field that looks like "sAMAccountName@domain",
|
||||
where domain is constructed from the domain components of
|
||||
the group DN.
|
||||
type: string
|
||||
type: object
|
||||
base:
|
||||
description: Base is the dn (distinguished name) that should be
|
||||
used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com".
|
||||
Optional, when not specified it will be based on the result
|
||||
of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
The default behavior searches your entire domain for groups.
|
||||
It may make sense to specify a subtree as a search base if you
|
||||
wish to exclude some groups for security reasons or to make
|
||||
searches faster.
|
||||
type: string
|
||||
filter:
|
||||
description: Filter is the ActiveDirectory search filter which
|
||||
should be applied when searching for groups for a user. The
|
||||
pattern "{}" must occur in the filter at least once and will
|
||||
be dynamically replaced by the dn (distinguished name) of the
|
||||
user entry found as a result of the user search. E.g. "member={}"
|
||||
or "&(objectClass=groupOfNames)(member={})". For more information
|
||||
about ActiveDirectory filters, see https://ldap.com/ldap-filters.
|
||||
Note that the dn (distinguished name) is not an attribute of
|
||||
an entry, so "dn={}" cannot be used. Optional. When not specified,
|
||||
the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})".
|
||||
This searches nested groups by default. Note that nested group
|
||||
search can be slow for some Active Directory servers. To disable
|
||||
it, you can set the filter to "(&(objectClass=group)(member={})"
|
||||
type: string
|
||||
skipGroupRefresh:
|
||||
description: "The user's group membership is refreshed as they
|
||||
interact with the supervisor to obtain new credentials (as their
|
||||
old credentials expire). This allows group membership changes
|
||||
to be quickly reflected into Kubernetes clusters. Since group
|
||||
membership is often used to bind authorization policies, it
|
||||
is important to keep the groups observed in Kubernetes clusters
|
||||
in-sync with the identity provider. \n In some environments,
|
||||
frequent group membership queries may result in a significant
|
||||
performance impact on the identity provider and/or the supervisor.
|
||||
The best approach to handle performance impacts is to tweak
|
||||
the group query to be more performant, for example by disabling
|
||||
nested group search or by using a more targeted group search
|
||||
base. \n If the group search query cannot be made performant
|
||||
and you are willing to have group memberships remain static
|
||||
for approximately a day, then set skipGroupRefresh to true.
|
||||
\ This is an insecure configuration as authorization policies
|
||||
that are bound to group membership will not notice if a user
|
||||
has been removed from a particular group until their next login.
|
||||
\n This is an experimental feature that may be removed or significantly
|
||||
altered in the future. Consumers of this configuration should
|
||||
carefully read all release notes before upgrading to ensure
|
||||
that the meaning of this field has not changed."
|
||||
type: boolean
|
||||
type: object
|
||||
host:
|
||||
description: 'Host is the hostname of this Active Directory identity
|
||||
provider, i.e., where to connect. For example: ldap.example.com:636.'
|
||||
minLength: 1
|
||||
type: string
|
||||
tls:
|
||||
description: TLS contains the connection settings for how to establish
|
||||
the connection to the Host.
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: X.509 Certificate Authority (base64-encoded PEM bundle).
|
||||
If omitted, a default set of system roots will be trusted.
|
||||
type: string
|
||||
type: object
|
||||
userSearch:
|
||||
description: UserSearch contains the configuration for searching for
|
||||
a user by name in Active Directory.
|
||||
properties:
|
||||
attributes:
|
||||
description: Attributes specifies how the user's information should
|
||||
be read from the ActiveDirectory entry which was found as the
|
||||
result of the user search.
|
||||
properties:
|
||||
uid:
|
||||
description: UID specifies the name of the attribute in the
|
||||
ActiveDirectory entry which whose value shall be used to
|
||||
uniquely identify the user within this ActiveDirectory provider
|
||||
after a successful authentication. Optional, when empty
|
||||
this defaults to "objectGUID".
|
||||
type: string
|
||||
username:
|
||||
description: Username specifies the name of the attribute
|
||||
in Active Directory entry whose value shall become the username
|
||||
of the user after a successful authentication. Optional,
|
||||
when empty this defaults to "userPrincipalName".
|
||||
type: string
|
||||
type: object
|
||||
base:
|
||||
description: Base is the dn (distinguished name) that should be
|
||||
used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
|
||||
Optional, when not specified it will be based on the result
|
||||
of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
The default behavior searches your entire domain for users.
|
||||
It may make sense to specify a subtree as a search base if you
|
||||
wish to exclude some users or to make searches faster.
|
||||
type: string
|
||||
filter:
|
||||
description: Filter is the search filter which should be applied
|
||||
when searching for users. The pattern "{}" must occur in the
|
||||
filter at least once and will be dynamically replaced by the
|
||||
username for which the search is being run. E.g. "mail={}" or
|
||||
"&(objectClass=person)(uid={})". For more information about
|
||||
LDAP filters, see https://ldap.com/ldap-filters. Note that the
|
||||
dn (distinguished name) is not an attribute of an entry, so
|
||||
"dn={}" cannot be used. Optional. When not specified, the default
|
||||
will be '(&(objectClass=person)(!(objectClass=computer))(!(showInAdvancedViewOnly=TRUE))(|(sAMAccountName={}")(mail={})(userPrincipalName={})(sAMAccountType=805306368))'
|
||||
This means that the user is a person, is not a computer, the
|
||||
sAMAccountType is for a normal user account, and is not shown
|
||||
in advanced view only (which would likely mean its a system
|
||||
created service account with advanced permissions). Also, either
|
||||
the sAMAccountName, the userPrincipalName, or the mail attribute
|
||||
matches the input username.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- host
|
||||
type: object
|
||||
status:
|
||||
description: Status of the identity provider.
|
||||
properties:
|
||||
conditions:
|
||||
description: Represents the observations of an identity provider's
|
||||
current state.
|
||||
items:
|
||||
description: Condition status of a resource (mirrored from the metav1.Condition
|
||||
type added in Kubernetes 1.19). In a future API version we can
|
||||
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the ActiveDirectoryIdentityProvider.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.4.0
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
name: ldapidentityproviders.idp.supervisor.pinniped.dev
|
||||
spec:
|
||||
@@ -111,6 +110,30 @@ spec:
|
||||
an entry, so "dn={}" cannot be used. Optional. When not specified,
|
||||
the default will act as if the Filter were specified as "member={}".
|
||||
type: string
|
||||
skipGroupRefresh:
|
||||
description: "The user's group membership is refreshed as they
|
||||
interact with the supervisor to obtain new credentials (as their
|
||||
old credentials expire). This allows group membership changes
|
||||
to be quickly reflected into Kubernetes clusters. Since group
|
||||
membership is often used to bind authorization policies, it
|
||||
is important to keep the groups observed in Kubernetes clusters
|
||||
in-sync with the identity provider. \n In some environments,
|
||||
frequent group membership queries may result in a significant
|
||||
performance impact on the identity provider and/or the supervisor.
|
||||
The best approach to handle performance impacts is to tweak
|
||||
the group query to be more performant, for example by disabling
|
||||
nested group search or by using a more targeted group search
|
||||
base. \n If the group search query cannot be made performant
|
||||
and you are willing to have group memberships remain static
|
||||
for approximately a day, then set skipGroupRefresh to true.
|
||||
\ This is an insecure configuration as authorization policies
|
||||
that are bound to group membership will not notice if a user
|
||||
has been removed from a particular group until their next login.
|
||||
\n This is an experimental feature that may be removed or significantly
|
||||
altered in the future. Consumers of this configuration should
|
||||
carefully read all release notes before upgrading to ensure
|
||||
that the meaning of this field has not changed."
|
||||
type: boolean
|
||||
type: object
|
||||
host:
|
||||
description: 'Host is the hostname of this LDAP identity provider,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.4.0
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
name: oidcidentityproviders.idp.supervisor.pinniped.dev
|
||||
spec:
|
||||
@@ -56,26 +55,150 @@ spec:
|
||||
the OAuth2 authorization request parameters to be used with this
|
||||
OIDC identity provider.
|
||||
properties:
|
||||
additionalAuthorizeParameters:
|
||||
description: additionalAuthorizeParameters are extra query parameters
|
||||
that should be included in the authorize request to your OIDC
|
||||
provider in the authorization request during an OIDC Authorization
|
||||
Code Flow. By default, no extra parameters are sent. The standard
|
||||
parameters that will be sent are "response_type", "scope", "client_id",
|
||||
"state", "nonce", "code_challenge", "code_challenge_method",
|
||||
and "redirect_uri". These parameters cannot be included in this
|
||||
setting. Additionally, the "hd" parameter cannot be included
|
||||
in this setting at this time. The "hd" parameter is used by
|
||||
Google's OIDC provider to provide a hint as to which "hosted
|
||||
domain" the user should use during login. However, Pinniped
|
||||
does not yet support validating the hosted domain in the resulting
|
||||
ID token, so it is not yet safe to use this feature of Google's
|
||||
OIDC provider with Pinniped. This setting does not influence
|
||||
the parameters sent to the token endpoint in the Resource Owner
|
||||
Password Credentials Grant. The Pinniped Supervisor requires
|
||||
that your OIDC provider returns refresh tokens to the Supervisor
|
||||
from the authorization flows. Some OIDC providers may require
|
||||
a certain value for the "prompt" parameter in order to properly
|
||||
request refresh tokens. See the documentation of your OIDC provider's
|
||||
authorization endpoint for its requirements for what to include
|
||||
in the request in order to receive a refresh token in the response,
|
||||
if anything. If your provider requires the prompt parameter
|
||||
to request a refresh token, then include it here. Also note
|
||||
that most providers also require a certain scope to be requested
|
||||
in order to receive refresh tokens. See the additionalScopes
|
||||
setting for more information about using scopes to request refresh
|
||||
tokens.
|
||||
items:
|
||||
description: Parameter is a key/value pair which represents
|
||||
a parameter in an HTTP request.
|
||||
properties:
|
||||
name:
|
||||
description: The name of the parameter. Required.
|
||||
minLength: 1
|
||||
type: string
|
||||
value:
|
||||
description: The value of the parameter.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
additionalScopes:
|
||||
description: AdditionalScopes are the scopes in addition to "openid"
|
||||
that will be requested as part of the authorization request
|
||||
flow with an OIDC identity provider. By default only the "openid"
|
||||
scope will be requested.
|
||||
description: 'additionalScopes are the additional scopes that
|
||||
will be requested from your OIDC provider in the authorization
|
||||
request during an OIDC Authorization Code Flow and in the token
|
||||
request during a Resource Owner Password Credentials Grant.
|
||||
Note that the "openid" scope will always be requested regardless
|
||||
of the value in this setting, since it is always required according
|
||||
to the OIDC spec. By default, when this field is not set, the
|
||||
Supervisor will request the following scopes: "openid", "offline_access",
|
||||
"email", and "profile". See https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
|
||||
for a description of the "profile" and "email" scopes. See https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
||||
for a description of the "offline_access" scope. This default
|
||||
value may change in future versions of Pinniped as the standard
|
||||
evolves, or as common patterns used by providers who implement
|
||||
the standard in the ecosystem evolve. By setting this list to
|
||||
anything other than an empty list, you are overriding the default
|
||||
value, so you may wish to include some of "offline_access",
|
||||
"email", and "profile" in your override list. If you do not
|
||||
want any of these scopes to be requested, you may set this list
|
||||
to contain only "openid". Some OIDC providers may also require
|
||||
a scope to get access to the user''s group membership, in which
|
||||
case you may wish to include it in this list. Sometimes the
|
||||
scope to request the user''s group membership is called "groups",
|
||||
but unfortunately this is not specified in the OIDC standard.
|
||||
Generally speaking, you should include any scopes required to
|
||||
cause the appropriate claims to be the returned by your OIDC
|
||||
provider in the ID token or userinfo endpoint results for those
|
||||
claims which you would like to use in the oidcClaims settings
|
||||
to determine the usernames and group memberships of your Kubernetes
|
||||
users. See your OIDC provider''s documentation for more information
|
||||
about what scopes are available to request claims. Additionally,
|
||||
the Pinniped Supervisor requires that your OIDC provider returns
|
||||
refresh tokens to the Supervisor from these authorization flows.
|
||||
For most OIDC providers, the scope required to receive refresh
|
||||
tokens will be "offline_access". See the documentation of your
|
||||
OIDC provider''s authorization and token endpoints for its requirements
|
||||
for what to include in the request in order to receive a refresh
|
||||
token in the response, if anything. Note that it may be safe
|
||||
to send "offline_access" even to providers which do not require
|
||||
it, since the provider may ignore scopes that it does not understand
|
||||
or require (see https://datatracker.ietf.org/doc/html/rfc6749#section-3.3).
|
||||
In the unusual case that you must avoid sending the "offline_access"
|
||||
scope, then you must override the default value of this setting.
|
||||
This is required if your OIDC provider will reject the request
|
||||
when it includes "offline_access" (e.g. GitLab''s OIDC provider).'
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
allowPasswordGrant:
|
||||
description: allowPasswordGrant, when true, will allow the use
|
||||
of OAuth 2.0's Resource Owner Password Credentials Grant (see
|
||||
https://datatracker.ietf.org/doc/html/rfc6749#section-4.3) to
|
||||
authenticate to the OIDC provider using a username and password
|
||||
without a web browser, in addition to the usual browser-based
|
||||
OIDC Authorization Code Flow. The Resource Owner Password Credentials
|
||||
Grant is not officially part of the OIDC specification, so it
|
||||
may not be supported by your OIDC provider. If your OIDC provider
|
||||
supports returning ID tokens from a Resource Owner Password
|
||||
Credentials Grant token request, then you can choose to set
|
||||
this field to true. This will allow end users to choose to present
|
||||
their username and password to the kubectl CLI (using the Pinniped
|
||||
plugin) to authenticate to the cluster, without using a web
|
||||
browser to log in as is customary in OIDC Authorization Code
|
||||
Flow. This may be convenient for users, especially for identities
|
||||
from your OIDC provider which are not intended to represent
|
||||
a human actor, such as service accounts performing actions in
|
||||
a CI/CD environment. Even if your OIDC provider supports it,
|
||||
you may wish to disable this behavior by setting this field
|
||||
to false when you prefer to only allow users of this OIDCIdentityProvider
|
||||
to log in via the browser-based OIDC Authorization Code Flow.
|
||||
Using the Resource Owner Password Credentials Grant means that
|
||||
the Pinniped CLI and Pinniped Supervisor will directly handle
|
||||
your end users' passwords (similar to LDAPIdentityProvider),
|
||||
and you will not be able to require multi-factor authentication
|
||||
or use the other web-based login features of your OIDC provider
|
||||
during Resource Owner Password Credentials Grant logins. allowPasswordGrant
|
||||
defaults to false.
|
||||
type: boolean
|
||||
type: object
|
||||
claims:
|
||||
description: Claims provides the names of token claims that will be
|
||||
used when inspecting an identity from this OIDC identity provider.
|
||||
properties:
|
||||
groups:
|
||||
description: Groups provides the name of the token claim that
|
||||
will be used to ascertain the groups to which an identity belongs.
|
||||
description: Groups provides the name of the ID token claim or
|
||||
userinfo endpoint response claim that will be used to ascertain
|
||||
the groups to which an identity belongs. By default, the identities
|
||||
will not include any group memberships when this setting is
|
||||
not configured.
|
||||
type: string
|
||||
username:
|
||||
description: Username provides the name of the token claim that
|
||||
will be used to ascertain an identity's username.
|
||||
description: Username provides the name of the ID token claim
|
||||
or userinfo endpoint response claim that will be used to ascertain
|
||||
an identity's username. When not set, the username will be an
|
||||
automatically constructed unique string which will include the
|
||||
issuer URL of your OIDC provider along with the value of the
|
||||
"sub" (subject) claim from the ID token.
|
||||
type: string
|
||||
type: object
|
||||
client:
|
||||
|
||||
@@ -40,6 +40,14 @@ rules:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [ldapidentityproviders/status]
|
||||
verbs: [get, patch, update]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [activedirectoryidentityproviders]
|
||||
verbs: [get, list, watch]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [activedirectoryidentityproviders/status]
|
||||
verbs: [get, patch, update]
|
||||
#! We want to be able to read pods/replicasets/deployment so we can learn who our deployment is to set
|
||||
#! as an owner reference.
|
||||
- apiGroups: [""]
|
||||
@@ -48,6 +56,9 @@ rules:
|
||||
- apiGroups: [apps]
|
||||
resources: [replicasets,deployments]
|
||||
verbs: [get]
|
||||
- apiGroups: [ coordination.k8s.io ]
|
||||
resources: [ leases ]
|
||||
verbs: [ create, get, update ]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
|
||||
#@ load("@ytt:assert", "assert")
|
||||
#@ load("helpers.lib.yaml", "labels", "deploymentPodLabel", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
|
||||
|
||||
#@ if data.values.service_http_nodeport_port or data.values.service_https_nodeport_port:
|
||||
#@ if hasattr(data.values, "service_http_nodeport_port"):
|
||||
#@ assert.fail('value "service_http_nodeport_port" has been renamed to "deprecated_service_http_nodeport_port" and will be removed in a future release')
|
||||
#@ end
|
||||
#@ if hasattr(data.values, "service_http_nodeport_nodeport"):
|
||||
#@ assert.fail('value "service_http_nodeport_nodeport" has been renamed to "deprecated_service_http_nodeport_nodeport" and will be removed in a future release')
|
||||
#@ end
|
||||
#@ if hasattr(data.values, "service_http_loadbalancer_port"):
|
||||
#@ assert.fail('value "service_http_loadbalancer_port" has been renamed to "deprecated_service_http_loadbalancer_port" and will be removed in a future release')
|
||||
#@ end
|
||||
#@ if hasattr(data.values, "service_http_clusterip_port"):
|
||||
#@ assert.fail('value "service_http_clusterip_port" has been renamed to "deprecated_service_http_clusterip_port" and will be removed in a future release')
|
||||
#@ end
|
||||
|
||||
#@ if data.values.deprecated_service_http_nodeport_port or data.values.service_https_nodeport_port:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -12,18 +26,20 @@ metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("nodeport")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: #@ data.values.app_name
|
||||
selector: #@ deploymentPodLabel()
|
||||
ports:
|
||||
#@ if data.values.service_http_nodeport_port:
|
||||
#@ if data.values.deprecated_service_http_nodeport_port:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: #@ data.values.service_http_nodeport_port
|
||||
port: #@ data.values.deprecated_service_http_nodeport_port
|
||||
targetPort: 8080
|
||||
#@ if data.values.service_http_nodeport_nodeport:
|
||||
nodePort: #@ data.values.service_http_nodeport_nodeport
|
||||
#@ if data.values.deprecated_service_http_nodeport_nodeport:
|
||||
nodePort: #@ data.values.deprecated_service_http_nodeport_nodeport
|
||||
#@ end
|
||||
#@ end
|
||||
#@ if data.values.service_https_nodeport_port:
|
||||
@@ -37,7 +53,7 @@ spec:
|
||||
#@ end
|
||||
#@ end
|
||||
|
||||
#@ if data.values.service_http_clusterip_port or data.values.service_https_clusterip_port:
|
||||
#@ if data.values.deprecated_service_http_clusterip_port or data.values.service_https_clusterip_port:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -45,14 +61,17 @@ metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("clusterip")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector: #@ defaultLabel()
|
||||
selector: #@ deploymentPodLabel()
|
||||
ports:
|
||||
#@ if data.values.service_http_clusterip_port:
|
||||
#@ if data.values.deprecated_service_http_clusterip_port:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: #@ data.values.service_http_clusterip_port
|
||||
port: #@ data.values.deprecated_service_http_clusterip_port
|
||||
targetPort: 8080
|
||||
#@ end
|
||||
#@ if data.values.service_https_clusterip_port:
|
||||
@@ -63,7 +82,7 @@ spec:
|
||||
#@ end
|
||||
#@ end
|
||||
|
||||
#@ if data.values.service_http_loadbalancer_port or data.values.service_https_loadbalancer_port:
|
||||
#@ if data.values.deprecated_service_http_loadbalancer_port or data.values.service_https_loadbalancer_port:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -71,17 +90,20 @@ metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("loadbalancer")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector: #@ defaultLabel()
|
||||
selector: #@ deploymentPodLabel()
|
||||
#@ if data.values.service_loadbalancer_ip:
|
||||
loadBalancerIP: #@ data.values.service_loadbalancer_ip
|
||||
#@ end
|
||||
ports:
|
||||
#@ if data.values.service_http_loadbalancer_port:
|
||||
#@ if data.values.deprecated_service_http_loadbalancer_port:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: #@ data.values.service_http_loadbalancer_port
|
||||
port: #@ data.values.deprecated_service_http_loadbalancer_port
|
||||
targetPort: 8080
|
||||
#@ end
|
||||
#@ if data.values.service_https_loadbalancer_port:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@data/values
|
||||
@@ -35,33 +35,95 @@ image_tag: latest
|
||||
#! Optional.
|
||||
image_pull_dockerconfigjson: #! e.g. {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}
|
||||
|
||||
#! Specify how to expose the Supervisor app's HTTP and/or HTTPS ports as a Service.
|
||||
#! Typically you would set a value for only one of the following service types, for either HTTP or HTTPS depending on your needs.
|
||||
#! An HTTP service should not be exposed outside the cluster. It would not be secure to serve OIDC endpoints to end users via HTTP.
|
||||
#! Setting any of these values means that a Service of that type will be created.
|
||||
#! Specify how to expose the Supervisor app's HTTPS port as a Service.
|
||||
#! Typically, you would set a value for only one of the following service types.
|
||||
#! Setting any of these values means that a Service of that type will be created. They are all optional.
|
||||
#! Note that all port numbers should be numbers (not strings), i.e. use ytt's `--data-value-yaml` instead of `--data-value`.
|
||||
service_http_nodeport_port: #! when specified, creates a NodePort Service with this `port` value, with port 8080 as its `targetPort`; e.g. 31234
|
||||
service_http_nodeport_nodeport: #! the `nodePort` value of the NodePort Service, optional when `service_http_nodeport_port` is specified; e.g. 31234
|
||||
service_http_loadbalancer_port: #! when specified, creates a LoadBalancer Service with this `port` value, with port 8080 as its `targetPort`; e.g. 8443
|
||||
service_http_clusterip_port: #! when specified, creates a ClusterIP Service with this `port` value, with port 8080 as its `targetPort`; e.g. 8443
|
||||
#! Several of these values have been deprecated and will be removed in a future release. Their names have been changed to
|
||||
#! mark them as deprecated and to make it obvious upon upgrade to anyone who was using them that they have been deprecated.
|
||||
deprecated_service_http_nodeport_port: #! will be removed in a future release; when specified, creates a NodePort Service with this `port` value, with port 8080 as its `targetPort`; e.g. 31234
|
||||
deprecated_service_http_nodeport_nodeport: #! will be removed in a future release; the `nodePort` value of the NodePort Service, optional when `deprecated_service_http_nodeport_port` is specified; e.g. 31234
|
||||
deprecated_service_http_loadbalancer_port: #! will be removed in a future release; when specified, creates a LoadBalancer Service with this `port` value, with port 8080 as its `targetPort`; e.g. 8443
|
||||
deprecated_service_http_clusterip_port: #! will be removed in a future release; when specified, creates a ClusterIP Service with this `port` value, with port 8080 as its `targetPort`; e.g. 8443
|
||||
service_https_nodeport_port: #! when specified, creates a NodePort Service with this `port` value, with port 8443 as its `targetPort`; e.g. 31243
|
||||
service_https_nodeport_nodeport: #! the `nodePort` value of the NodePort Service, optional when `service_http_nodeport_port` is specified; e.g. 31243
|
||||
service_https_nodeport_nodeport: #! the `nodePort` value of the NodePort Service, optional when `service_https_nodeport_port` is specified; e.g. 31243
|
||||
service_https_loadbalancer_port: #! when specified, creates a LoadBalancer Service with this `port` value, with port 8443 as its `targetPort`; e.g. 8443
|
||||
service_https_clusterip_port: #! when specified, creates a ClusterIP Service with this `port` value, with port 8443 as its `targetPort`; e.g. 8443
|
||||
#! The `loadBalancerIP` value of the LoadBalancer Service.
|
||||
#! Ignored unless service_http_loadbalancer_port and/or service_https_loadbalancer_port are provided.
|
||||
#! Ignored unless service_https_loadbalancer_port is provided.
|
||||
#! Optional.
|
||||
service_loadbalancer_ip: #! e.g. 1.2.3.4
|
||||
|
||||
#! Specify the verbosity of logging: info ("nice to know" information), debug (developer
|
||||
#! information), trace (timing information), all (kitchen sink).
|
||||
#! Specify the verbosity of logging: info ("nice to know" information), debug (developer information), trace (timing information),
|
||||
#! or all (kitchen sink). Do not use trace or all on production systems, as credentials may get logged.
|
||||
log_level: #! By default, when this value is left unset, only warnings and errors are printed. There is no way to suppress warning and error logs.
|
||||
|
||||
run_as_user: 1001 #! run_as_user specifies the user ID that will own the process
|
||||
run_as_group: 1001 #! run_as_group specifies the group ID that will own the process
|
||||
run_as_user: 65532 #! run_as_user specifies the user ID that will own the process, see the Dockerfile for the reasoning behind this choice
|
||||
run_as_group: 65532 #! run_as_group specifies the group ID that will own the process, see the Dockerfile for the reasoning behind this choice
|
||||
|
||||
#! Specify the API group suffix for all Pinniped API groups. By default, this is set to
|
||||
#! pinniped.dev, so Pinniped API groups will look like foo.pinniped.dev,
|
||||
#! authentication.concierge.pinniped.dev, etc. As an example, if this is set to tuna.io, then
|
||||
#! Pinniped API groups will look like foo.tuna.io. authentication.concierge.tuna.io, etc.
|
||||
api_group_suffix: pinniped.dev
|
||||
|
||||
#! Set the standard golang HTTPS_PROXY and NO_PROXY environment variables on the Supervisor containers.
|
||||
#! These will be used when the Supervisor makes backend-to-backend calls to upstream identity providers using HTTPS,
|
||||
#! e.g. when the Supervisor fetches discovery documents, JWKS keys, and tokens from an upstream OIDC Provider.
|
||||
#! The Supervisor never makes insecure HTTP calls, so there is no reason to set HTTP_PROXY.
|
||||
#! Optional.
|
||||
https_proxy: #! e.g. http://proxy.example.com
|
||||
no_proxy: "$(KUBERNETES_SERVICE_HOST),169.254.169.254,127.0.0.1,localhost,.svc,.cluster.local" #! do not proxy Kubernetes endpoints
|
||||
|
||||
#! Control the HTTP and HTTPS listeners of the Supervisor.
|
||||
#!
|
||||
#! The schema of this config is as follows:
|
||||
#!
|
||||
#! endpoints:
|
||||
#! https:
|
||||
#! network: tcp | unix | disabled
|
||||
#! address: host:port when network=tcp or /pinniped_socket/socketfile.sock when network=unix
|
||||
#! http:
|
||||
#! network: same as above
|
||||
#! address: same as above, except that when network=tcp then the address is only allowed to bind to loopback interfaces
|
||||
#!
|
||||
#! Setting network to disabled turns off that particular listener.
|
||||
#! See https://pkg.go.dev/net#Listen and https://pkg.go.dev/net#Dial for a description of what can be
|
||||
#! specified in the address parameter based on the given network parameter. To aid in the use of unix
|
||||
#! domain sockets, a writable empty dir volume is mounted at /pinniped_socket when network is set to "unix."
|
||||
#!
|
||||
#! The current defaults are:
|
||||
#!
|
||||
#! endpoints:
|
||||
#! https:
|
||||
#! network: tcp
|
||||
#! address: :8443
|
||||
#! http:
|
||||
#! network: disabled
|
||||
#!
|
||||
#! These defaults mean: For HTTPS listening, bind to all interfaces using TCP on port 8443.
|
||||
#! Disable HTTP listening by default.
|
||||
#!
|
||||
#! The HTTP listener can only be bound to loopback interfaces. This allows the listener to accept
|
||||
#! traffic from within the pod, e.g. from a service mesh sidecar. The HTTP listener should not be
|
||||
#! used to accept traffic from outside the pod, since that would mean that the network traffic could be
|
||||
#! transmitted unencrypted. The HTTPS listener should be used instead to accept traffic from outside the pod.
|
||||
#! Ingresses and load balancers that terminate TLS connections should re-encrypt the data and route traffic
|
||||
#! to the HTTPS listener. Unix domain sockets may also be used for integrations with service meshes.
|
||||
#!
|
||||
#! Changing the HTTPS port number must be accompanied by matching changes to the service and deployment
|
||||
#! manifests. Changes to the HTTPS listener must be coordinated with the deployment health checks.
|
||||
#!
|
||||
#! Optional.
|
||||
endpoints:
|
||||
|
||||
#! Optionally override the validation on the endpoints.http value which checks that only loopback interfaces are used.
|
||||
#! When deprecated_insecure_accept_external_unencrypted_http_requests is true, the HTTP listener is allowed to bind to any
|
||||
#! interface, including interfaces that are listening for traffic from outside the pod. This value is being introduced
|
||||
#! to ease the transition to the new loopback interface validation for the HTTP port for any users who need more time
|
||||
#! to change their ingress strategy to avoid using plain HTTP into the Supervisor pods.
|
||||
#! This value is immediately deprecated upon its introduction. It will be removed in some future release, at which time
|
||||
#! traffic from outside the pod will need to be sent to the HTTPS listener instead, with no simple workaround available.
|
||||
#! Allowed values are true (boolean), "true" (string), false (boolean), and "false" (string). The default is false.
|
||||
#! Optional.
|
||||
deprecated_insecure_accept_external_unencrypted_http_requests: false
|
||||
|
||||
@@ -31,3 +31,12 @@ metadata:
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("ldapidentityproviders.idp.supervisor")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
|
||||
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"activedirectoryidentityproviders.idp.supervisor.pinniped.dev"}}), expects=1
|
||||
---
|
||||
metadata:
|
||||
#@overlay/match missing_ok=True
|
||||
labels: #@ labels()
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("activedirectoryidentityproviders.idp.supervisor")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
|
||||
237
generated/1.17/README.adoc
generated
237
generated/1.17/README.adoc
generated
@@ -24,7 +24,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authenticatio
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition"]
|
||||
==== Condition
|
||||
|
||||
|
||||
Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -36,7 +36,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authenticatio
|
||||
|===
|
||||
| Field | Description
|
||||
| *`type`* __string__ | type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
| *`status`* __ConditionStatus__ | status of the condition, one of True, False, Unknown.
|
||||
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-conditionstatus[$$ConditionStatus$$]__ | status of the condition, one of True, False, Unknown.
|
||||
| *`observedGeneration`* __integer__ | observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
|
||||
| *`lastTransitionTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#time-v1-meta[$$Time$$]__ | lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
| *`reason`* __string__ | reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
|
||||
@@ -47,7 +47,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authenticatio
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-conditionstatus"]
|
||||
==== ConditionStatus (string)
|
||||
|
||||
|
||||
ConditionStatus is effectively an enum type for Condition.Status.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -137,7 +137,7 @@ JWTTokenClaims allows customization of the claims that will be mapped to user id
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-tlsspec"]
|
||||
==== TLSSpec
|
||||
|
||||
|
||||
Configuration for configuring TLS on various authenticators.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -240,7 +240,7 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerfrontend"]
|
||||
==== CredentialIssuerFrontend
|
||||
|
||||
|
||||
CredentialIssuerFrontend describes how to connect using a particular integration strategy.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -259,7 +259,7 @@ CredentialIssuer describes the configuration and status of the Pinniped Concierg
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerkubeconfiginfo"]
|
||||
==== CredentialIssuerKubeConfigInfo
|
||||
|
||||
|
||||
CredentialIssuerKubeConfigInfo provides the information needed to form a valid Pinniped-based kubeconfig using this credential issuer. This type is deprecated and will be removed in a future version.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -314,7 +314,7 @@ CredentialIssuerStatus describes the status of the Concierge.
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstrategy"]
|
||||
==== CredentialIssuerStrategy
|
||||
|
||||
|
||||
CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -336,7 +336,7 @@ CredentialIssuerStatus describes the status of the Concierge.
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyinfo"]
|
||||
==== ImpersonationProxyInfo
|
||||
|
||||
|
||||
ImpersonationProxyInfo describes the parameters for the impersonation proxy on this Concierge.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -354,7 +354,7 @@ CredentialIssuerStatus describes the status of the Concierge.
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxymode"]
|
||||
==== ImpersonationProxyMode (string)
|
||||
|
||||
|
||||
ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -376,7 +376,7 @@ ImpersonationProxyServiceSpec describes how the Concierge should provision a Ser
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`type`* __ImpersonationProxyServiceType__ | Type specifies the type of Service to provision for the impersonation proxy.
|
||||
| *`type`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicetype[$$ImpersonationProxyServiceType$$]__ | Type specifies the type of Service to provision for the impersonation proxy.
|
||||
If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status.
|
||||
| *`loadBalancerIP`* __string__ | LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. This is not supported on all cloud providers.
|
||||
| *`annotations`* __object (keys:string, values:string)__ | Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service.
|
||||
@@ -386,7 +386,7 @@ ImpersonationProxyServiceSpec describes how the Concierge should provision a Ser
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicetype"]
|
||||
==== ImpersonationProxyServiceType (string)
|
||||
|
||||
|
||||
ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -398,7 +398,7 @@ ImpersonationProxyServiceSpec describes how the Concierge should provision a Ser
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec"]
|
||||
==== ImpersonationProxySpec
|
||||
|
||||
|
||||
ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -408,17 +408,17 @@ ImpersonationProxyServiceSpec describes how the Concierge should provision a Ser
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running.
|
||||
| *`mode`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxymode[$$ImpersonationProxyMode$$]__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running.
|
||||
| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients.
|
||||
| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will be served using the external name of the LoadBalancer service or the cluster service DNS name.
|
||||
This field must be non-empty when spec.impersonationProxy.service.mode is "None".
|
||||
This field must be non-empty when spec.impersonationProxy.service.type is "None".
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"]
|
||||
==== TokenCredentialRequestAPIInfo
|
||||
|
||||
|
||||
TokenCredentialRequestAPIInfo describes the parameters for the TokenCredentialRequest API on this Concierge.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -538,7 +538,7 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an
|
||||
| *`secretName`* __string__ | SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains the TLS serving certificate for the HTTPS endpoints served by this FederationDomain. When provided, the TLS Secret named here must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
|
||||
Server Name Indication (SNI) is an extension to the Transport Layer Security (TLS) supported by all major browsers.
|
||||
SecretName is required if you would like to use different TLS certificates for issuers of different hostnames. SNI requests do not include port numbers, so all issuers with the same DNS hostname must use the same SecretName value even if they have different port numbers.
|
||||
SecretName is not required when you would like to use only the HTTP endpoints (e.g. when terminating TLS at an Ingress). It is also not required when you would like all requests to this OIDC Provider's HTTPS endpoints to use the default TLS certificate, which is configured elsewhere.
|
||||
SecretName is not required when you would like to use only the HTTP endpoints (e.g. when the HTTP listener is configured to listen on loopback interfaces or UNIX domain sockets for traffic from a service mesh sidecar). It is also not required when you would like all requests to this OIDC Provider's HTTPS endpoints to use the default TLS certificate, which is configured elsewhere.
|
||||
When your Issuer URL's host is an IP address, then this field is ignored. SNI does not work for IP addresses.
|
||||
|===
|
||||
|
||||
@@ -748,13 +748,169 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity pro
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition"]
|
||||
==== Condition
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovider"]
|
||||
==== ActiveDirectoryIdentityProvider
|
||||
|
||||
ActiveDirectoryIdentityProvider describes the configuration of an upstream Microsoft Active Directory identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderlist[$$ActiveDirectoryIdentityProviderList$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
|
||||
|
||||
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]__ | Spec for configuring the identity provider.
|
||||
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderstatus[$$ActiveDirectoryIdentityProviderStatus$$]__ | Status of the identity provider.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderbind"]
|
||||
==== ActiveDirectoryIdentityProviderBind
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an Active Directory bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearch"]
|
||||
==== ActiveDirectoryIdentityProviderGroupSearch
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". Optional, when not specified it will be based on the result of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse). The default behavior searches your entire domain for groups. It may make sense to specify a subtree as a search base if you wish to exclude some groups for security reasons or to make searches faster.
|
||||
| *`filter`* __string__ | Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})". This searches nested groups by default. Note that nested group search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})"
|
||||
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearchattributes[$$ActiveDirectoryIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as the result of the group search.
|
||||
| *`skipGroupRefresh`* __boolean__ | The user's group membership is refreshed as they interact with the supervisor to obtain new credentials (as their old credentials expire). This allows group membership changes to be quickly reflected into Kubernetes clusters. Since group membership is often used to bind authorization policies, it is important to keep the groups observed in Kubernetes clusters in-sync with the identity provider.
|
||||
In some environments, frequent group membership queries may result in a significant performance impact on the identity provider and/or the supervisor. The best approach to handle performance impacts is to tweak the group query to be more performant, for example by disabling nested group search or by using a more targeted group search base.
|
||||
If the group search query cannot be made performant and you are willing to have group memberships remain static for approximately a day, then set skipGroupRefresh to true. This is an insecure configuration as authorization policies that are bound to group membership will not notice if a user has been removed from a particular group until their next login.
|
||||
This is an experimental feature that may be removed or significantly altered in the future. Consumers of this configuration should carefully read all release notes before upgrading to ensure that the meaning of this field has not changed.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearchattributes"]
|
||||
==== ActiveDirectoryIdentityProviderGroupSearchAttributes
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearch[$$ActiveDirectoryIdentityProviderGroupSearch$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`groupName`* __string__ | GroupName specifies the name of the attribute in the Active Directory entries whose value shall become a group name in the user's list of groups after a successful authentication. The value of this field is case-sensitive and must match the case of the attribute name returned by the ActiveDirectory server in the user's entry. E.g. "cn" for common name. Distinguished names can be used by specifying lower-case "dn". Optional. When not specified, this defaults to a custom field that looks like "sAMAccountName@domain", where domain is constructed from the domain components of the group DN.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec"]
|
||||
==== ActiveDirectoryIdentityProviderSpec
|
||||
|
||||
Spec for configuring an ActiveDirectory identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovider[$$ActiveDirectoryIdentityProvider$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`host`* __string__ | Host is the hostname of this Active Directory identity provider, i.e., where to connect. For example: ldap.example.com:636.
|
||||
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
|
||||
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderbind[$$ActiveDirectoryIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the ActiveDirectory server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
|
||||
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearch[$$ActiveDirectoryIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in Active Directory.
|
||||
| *`groupSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearch[$$ActiveDirectoryIdentityProviderGroupSearch$$]__ | GroupSearch contains the configuration for searching for a user's group membership in ActiveDirectory.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderstatus"]
|
||||
==== ActiveDirectoryIdentityProviderStatus
|
||||
|
||||
Status of an Active Directory identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovider[$$ActiveDirectoryIdentityProvider$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`phase`* __ActiveDirectoryIdentityProviderPhase__ | Phase summarizes the overall status of the ActiveDirectoryIdentityProvider.
|
||||
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearch"]
|
||||
==== ActiveDirectoryIdentityProviderUserSearch
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". Optional, when not specified it will be based on the result of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse). The default behavior searches your entire domain for users. It may make sense to specify a subtree as a search base if you wish to exclude some users or to make searches faster.
|
||||
| *`filter`* __string__ | Filter is the search filter which should be applied when searching for users. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will be '(&(objectClass=person)(!(objectClass=computer))(!(showInAdvancedViewOnly=TRUE))(\|(sAMAccountName={}")(mail={})(userPrincipalName={})(sAMAccountType=805306368))' This means that the user is a person, is not a computer, the sAMAccountType is for a normal user account, and is not shown in advanced view only (which would likely mean its a system created service account with advanced permissions). Also, either the sAMAccountName, the userPrincipalName, or the mail attribute matches the input username.
|
||||
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearchattributes[$$ActiveDirectoryIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the ActiveDirectory entry which was found as the result of the user search.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearchattributes"]
|
||||
==== ActiveDirectoryIdentityProviderUserSearchAttributes
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearch[$$ActiveDirectoryIdentityProviderUserSearch$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`username`* __string__ | Username specifies the name of the attribute in Active Directory entry whose value shall become the username of the user after a successful authentication. Optional, when empty this defaults to "userPrincipalName".
|
||||
| *`uid`* __string__ | UID specifies the name of the attribute in the ActiveDirectory entry which whose value shall be used to uniquely identify the user within this ActiveDirectory provider after a successful authentication. Optional, when empty this defaults to "objectGUID".
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition"]
|
||||
==== Condition
|
||||
|
||||
Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderstatus[$$ActiveDirectoryIdentityProviderStatus$$]
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus[$$OIDCIdentityProviderStatus$$]
|
||||
****
|
||||
@@ -763,7 +919,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity pro
|
||||
|===
|
||||
| Field | Description
|
||||
| *`type`* __string__ | type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
| *`status`* __ConditionStatus__ | status of the condition, one of True, False, Unknown.
|
||||
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-conditionstatus[$$ConditionStatus$$]__ | status of the condition, one of True, False, Unknown.
|
||||
| *`observedGeneration`* __integer__ | observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
|
||||
| *`lastTransitionTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#time-v1-meta[$$Time$$]__ | lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
| *`reason`* __string__ | reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
|
||||
@@ -774,7 +930,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity pro
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-conditionstatus"]
|
||||
==== ConditionStatus (string)
|
||||
|
||||
|
||||
ConditionStatus is effectively an enum type for Condition.Status.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -836,6 +992,10 @@ LDAPIdentityProvider describes the configuration of an upstream Lightweight Dire
|
||||
| *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". When not specified, no group search will be performed and authenticated users will not belong to any groups from the LDAP provider. Also, when not specified, the values of Filter and Attributes are ignored.
|
||||
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}".
|
||||
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes[$$LDAPIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each LDAP entry which was found as the result of the group search.
|
||||
| *`skipGroupRefresh`* __boolean__ | The user's group membership is refreshed as they interact with the supervisor to obtain new credentials (as their old credentials expire). This allows group membership changes to be quickly reflected into Kubernetes clusters. Since group membership is often used to bind authorization policies, it is important to keep the groups observed in Kubernetes clusters in-sync with the identity provider.
|
||||
In some environments, frequent group membership queries may result in a significant performance impact on the identity provider and/or the supervisor. The best approach to handle performance impacts is to tweak the group query to be more performant, for example by disabling nested group search or by using a more targeted group search base.
|
||||
If the group search query cannot be made performant and you are willing to have group memberships remain static for approximately a day, then set skipGroupRefresh to true. This is an insecure configuration as authorization policies that are bound to group membership will not notice if a user has been removed from a particular group until their next login.
|
||||
This is an experimental feature that may be removed or significantly altered in the future. Consumers of this configuration should carefully read all release notes before upgrading to ensure that the meaning of this field has not changed.
|
||||
|===
|
||||
|
||||
|
||||
@@ -947,7 +1107,9 @@ OIDCAuthorizationConfig provides information about how to form the OAuth2 author
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`additionalScopes`* __string array__ | AdditionalScopes are the scopes in addition to "openid" that will be requested as part of the authorization request flow with an OIDC identity provider. By default only the "openid" scope will be requested.
|
||||
| *`additionalScopes`* __string array__ | additionalScopes are the additional scopes that will be requested from your OIDC provider in the authorization request during an OIDC Authorization Code Flow and in the token request during a Resource Owner Password Credentials Grant. Note that the "openid" scope will always be requested regardless of the value in this setting, since it is always required according to the OIDC spec. By default, when this field is not set, the Supervisor will request the following scopes: "openid", "offline_access", "email", and "profile". See https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims for a description of the "profile" and "email" scopes. See https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess for a description of the "offline_access" scope. This default value may change in future versions of Pinniped as the standard evolves, or as common patterns used by providers who implement the standard in the ecosystem evolve. By setting this list to anything other than an empty list, you are overriding the default value, so you may wish to include some of "offline_access", "email", and "profile" in your override list. If you do not want any of these scopes to be requested, you may set this list to contain only "openid". Some OIDC providers may also require a scope to get access to the user's group membership, in which case you may wish to include it in this list. Sometimes the scope to request the user's group membership is called "groups", but unfortunately this is not specified in the OIDC standard. Generally speaking, you should include any scopes required to cause the appropriate claims to be the returned by your OIDC provider in the ID token or userinfo endpoint results for those claims which you would like to use in the oidcClaims settings to determine the usernames and group memberships of your Kubernetes users. See your OIDC provider's documentation for more information about what scopes are available to request claims. Additionally, the Pinniped Supervisor requires that your OIDC provider returns refresh tokens to the Supervisor from these authorization flows. For most OIDC providers, the scope required to receive refresh tokens will be "offline_access". See the documentation of your OIDC provider's authorization and token endpoints for its requirements for what to include in the request in order to receive a refresh token in the response, if anything. Note that it may be safe to send "offline_access" even to providers which do not require it, since the provider may ignore scopes that it does not understand or require (see https://datatracker.ietf.org/doc/html/rfc6749#section-3.3). In the unusual case that you must avoid sending the "offline_access" scope, then you must override the default value of this setting. This is required if your OIDC provider will reject the request when it includes "offline_access" (e.g. GitLab's OIDC provider).
|
||||
| *`additionalAuthorizeParameters`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-parameter[$$Parameter$$] array__ | additionalAuthorizeParameters are extra query parameters that should be included in the authorize request to your OIDC provider in the authorization request during an OIDC Authorization Code Flow. By default, no extra parameters are sent. The standard parameters that will be sent are "response_type", "scope", "client_id", "state", "nonce", "code_challenge", "code_challenge_method", and "redirect_uri". These parameters cannot be included in this setting. Additionally, the "hd" parameter cannot be included in this setting at this time. The "hd" parameter is used by Google's OIDC provider to provide a hint as to which "hosted domain" the user should use during login. However, Pinniped does not yet support validating the hosted domain in the resulting ID token, so it is not yet safe to use this feature of Google's OIDC provider with Pinniped. This setting does not influence the parameters sent to the token endpoint in the Resource Owner Password Credentials Grant. The Pinniped Supervisor requires that your OIDC provider returns refresh tokens to the Supervisor from the authorization flows. Some OIDC providers may require a certain value for the "prompt" parameter in order to properly request refresh tokens. See the documentation of your OIDC provider's authorization endpoint for its requirements for what to include in the request in order to receive a refresh token in the response, if anything. If your provider requires the prompt parameter to request a refresh token, then include it here. Also note that most providers also require a certain scope to be requested in order to receive refresh tokens. See the additionalScopes setting for more information about using scopes to request refresh tokens.
|
||||
| *`allowPasswordGrant`* __boolean__ | allowPasswordGrant, when true, will allow the use of OAuth 2.0's Resource Owner Password Credentials Grant (see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3) to authenticate to the OIDC provider using a username and password without a web browser, in addition to the usual browser-based OIDC Authorization Code Flow. The Resource Owner Password Credentials Grant is not officially part of the OIDC specification, so it may not be supported by your OIDC provider. If your OIDC provider supports returning ID tokens from a Resource Owner Password Credentials Grant token request, then you can choose to set this field to true. This will allow end users to choose to present their username and password to the kubectl CLI (using the Pinniped plugin) to authenticate to the cluster, without using a web browser to log in as is customary in OIDC Authorization Code Flow. This may be convenient for users, especially for identities from your OIDC provider which are not intended to represent a human actor, such as service accounts performing actions in a CI/CD environment. Even if your OIDC provider supports it, you may wish to disable this behavior by setting this field to false when you prefer to only allow users of this OIDCIdentityProvider to log in via the browser-based OIDC Authorization Code Flow. Using the Resource Owner Password Credentials Grant means that the Pinniped CLI and Pinniped Supervisor will directly handle your end users' passwords (similar to LDAPIdentityProvider), and you will not be able to require multi-factor authentication or use the other web-based login features of your OIDC provider during Resource Owner Password Credentials Grant logins. allowPasswordGrant defaults to false.
|
||||
|===
|
||||
|
||||
|
||||
@@ -964,8 +1126,8 @@ OIDCClaims provides a mapping from upstream claims into identities.
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`groups`* __string__ | Groups provides the name of the token claim that will be used to ascertain the groups to which an identity belongs.
|
||||
| *`username`* __string__ | Username provides the name of the token claim that will be used to ascertain an identity's username.
|
||||
| *`groups`* __string__ | Groups provides the name of the ID token claim or userinfo endpoint response claim that will be used to ascertain the groups to which an identity belongs. By default, the identities will not include any group memberships when this setting is not configured.
|
||||
| *`username`* __string__ | Username provides the name of the ID token claim or userinfo endpoint response claim that will be used to ascertain an identity's username. When not set, the username will be an automatically constructed unique string which will include the issuer URL of your OIDC provider along with the value of the "sub" (subject) claim from the ID token.
|
||||
|===
|
||||
|
||||
|
||||
@@ -1011,7 +1173,7 @@ OIDCIdentityProvider describes the configuration of an upstream OpenID Connect i
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec"]
|
||||
==== OIDCIdentityProviderSpec
|
||||
|
||||
Spec for configuring an OIDC identity provider.
|
||||
OIDCIdentityProviderSpec is the spec for configuring an OIDC identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -1032,7 +1194,7 @@ Spec for configuring an OIDC identity provider.
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus"]
|
||||
==== OIDCIdentityProviderStatus
|
||||
|
||||
Status of an OIDC identity provider.
|
||||
OIDCIdentityProviderStatus is the status of an OIDC identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -1047,13 +1209,32 @@ Status of an OIDC identity provider.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec"]
|
||||
==== TLSSpec
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-parameter"]
|
||||
==== Parameter
|
||||
|
||||
Parameter is a key/value pair which represents a parameter in an HTTP request.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcauthorizationconfig[$$OIDCAuthorizationConfig$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`name`* __string__ | The name of the parameter. Required.
|
||||
| *`value`* __string__ | The value of the parameter.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec"]
|
||||
==== TLSSpec
|
||||
|
||||
Configuration for TLS parameters related to identity provider integration.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -61,6 +61,8 @@ type JWTTokenClaims struct {
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
|
||||
// +kubebuilder:printcolumn:name="Audience",type=string,JSONPath=`.spec.audience`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type JWTAuthenticator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -33,6 +33,7 @@ type WebhookAuthenticatorSpec struct {
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.spec.endpoint`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type WebhookAuthenticator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
@@ -1,6 +1,7 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user