mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-18 03:33:09 +00:00
Compare commits
1628 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
174b3026c7 | ||
|
|
e009d22900 | ||
|
|
4b7fbc144e | ||
|
|
98621c76b5 | ||
|
|
6d8aed7f40 | ||
|
|
3031a070d1 | ||
|
|
ff71d7e768 | ||
|
|
a82e32057a | ||
|
|
d99563c85c | ||
|
|
54d530784d | ||
|
|
44509d016e | ||
|
|
3c4c272607 | ||
|
|
cbce243d57 | ||
|
|
b04ae898ca | ||
|
|
bc88faa50d | ||
|
|
f703d096d6 | ||
|
|
9d1c65c3e6 | ||
|
|
10444fb9a3 | ||
|
|
ebfd7d0600 | ||
|
|
b2747a0ebe | ||
|
|
e824e74fcc | ||
|
|
2394960a89 | ||
|
|
1a5a1eefe4 | ||
|
|
1350fb71f0 | ||
|
|
ca7d53ba0a | ||
|
|
0a5bc94c6e | ||
|
|
ca3c669230 | ||
|
|
5218c20c76 | ||
|
|
cba4e2a2e8 | ||
|
|
4b77a46a6f | ||
|
|
db1e85c412 | ||
|
|
a4fdce1077 | ||
|
|
f2c1eda663 | ||
|
|
fd05cbb4b1 | ||
|
|
22c63919af | ||
|
|
70cb9b64b9 | ||
|
|
3447e28fee | ||
|
|
05db85cb2d | ||
|
|
2cfa610249 | ||
|
|
fa5f75464d | ||
|
|
718b970bc2 | ||
|
|
9be6bb0b94 | ||
|
|
6e87caaa6a | ||
|
|
270594cdb1 | ||
|
|
b1b4bbaa4a | ||
|
|
9aa3f7496a | ||
|
|
7f14ac7863 | ||
|
|
f001df3978 | ||
|
|
2f68041c88 | ||
|
|
513f1cf7d9 | ||
|
|
d68761e144 | ||
|
|
92172ca150 | ||
|
|
c6d8539b8a | ||
|
|
70c3c62021 | ||
|
|
1623b2c46e | ||
|
|
adc0dba8e5 | ||
|
|
6de6d17429 | ||
|
|
c51fde6c5a | ||
|
|
cf63d782d4 | ||
|
|
577797d569 | ||
|
|
e427a5202e | ||
|
|
44893e6b0d | ||
|
|
7090fb7038 | ||
|
|
892ffead6c | ||
|
|
9bbdf59828 | ||
|
|
bf416d59c8 | ||
|
|
95e4ec9d88 | ||
|
|
097f3ec5fe | ||
|
|
301c717d17 | ||
|
|
e20c5beb47 | ||
|
|
f96b17abbf | ||
|
|
71295c619d | ||
|
|
bf2b28a755 | ||
|
|
62dd4ab758 | ||
|
|
ff38d82745 | ||
|
|
47e1f7e465 | ||
|
|
4fe8167f60 | ||
|
|
8f583447ca | ||
|
|
9c1cc8db50 | ||
|
|
089e8cf740 | ||
|
|
a038aeb8f0 | ||
|
|
088f88bdc0 | ||
|
|
3a02eec765 | ||
|
|
1c1b3b7f2e | ||
|
|
b387a2cae9 | ||
|
|
af8d7335fd | ||
|
|
07ed9e95cd | ||
|
|
8d56e06f1c | ||
|
|
17cb4c2ee5 | ||
|
|
cd3f55c019 | ||
|
|
cf700382fe | ||
|
|
da26da1bd5 | ||
|
|
679a40b847 | ||
|
|
6d8ad5f7a9 | ||
|
|
3c28171525 | ||
|
|
83696fd023 | ||
|
|
cc4a148c70 | ||
|
|
52622d5e4c | ||
|
|
64e5e20010 | ||
|
|
2a83d00373 | ||
|
|
fdfe2a3c9f | ||
|
|
04f0c559db | ||
|
|
9a97e20c40 | ||
|
|
9e742d2d84 | ||
|
|
4c134a410d | ||
|
|
cd431c2a8e | ||
|
|
96ef1b75a7 | ||
|
|
63d56f3abb | ||
|
|
de273ea68a | ||
|
|
b4758c8e4f | ||
|
|
5258bd542c | ||
|
|
0656a2dde9 | ||
|
|
9d681f2745 | ||
|
|
d57005c42a | ||
|
|
6de6bcd81a | ||
|
|
d1a14413fb | ||
|
|
c8a7c10793 | ||
|
|
fa0c63fc40 | ||
|
|
d8a439acc3 | ||
|
|
1254f730f2 | ||
|
|
69281a4620 | ||
|
|
02f7ad2fff | ||
|
|
2e3e0eed8e | ||
|
|
72cea70967 | ||
|
|
1428444c24 | ||
|
|
7276a1df53 | ||
|
|
33c68e2e7d | ||
|
|
0986c90678 | ||
|
|
986ebe0063 | ||
|
|
9b24ecad19 | ||
|
|
2e4f719419 | ||
|
|
f45e0aeb3d | ||
|
|
e743beac53 | ||
|
|
b294c7657a | ||
|
|
524989e16b | ||
|
|
a6b2905c3a | ||
|
|
aa2bdcb8dd | ||
|
|
a315cc4977 | ||
|
|
b8e7a64afe | ||
|
|
b50da60c84 | ||
|
|
dfc4c38259 | ||
|
|
dce1f5b208 | ||
|
|
c600cf7949 | ||
|
|
32a29da86c | ||
|
|
9ac9930650 | ||
|
|
1f01c394f3 | ||
|
|
a41ca92b8e | ||
|
|
f75eef16b4 | ||
|
|
2e801dc275 | ||
|
|
b8b460f98a | ||
|
|
39588c3bb4 | ||
|
|
79035bc2b6 | ||
|
|
85c94116ce | ||
|
|
7e8ea9f1fb | ||
|
|
a0e38fe8c1 | ||
|
|
f9c3d932c1 | ||
|
|
861c501ea8 | ||
|
|
e129a569ba | ||
|
|
fd42da225d | ||
|
|
b139a458fd | ||
|
|
68a2df41ed | ||
|
|
2cbdd60022 | ||
|
|
0e12f90749 | ||
|
|
7e132a5dcd | ||
|
|
910326f1ea | ||
|
|
6057b0c912 | ||
|
|
2845829fb4 | ||
|
|
653b6d5b0d | ||
|
|
7ef58a9132 | ||
|
|
cf98c69c0d | ||
|
|
62f6d8516b | ||
|
|
604ecc583c | ||
|
|
a010794873 | ||
|
|
c7462bf1f6 | ||
|
|
b396d12456 | ||
|
|
695ea3a165 | ||
|
|
d625ada06a | ||
|
|
0f48620d9c | ||
|
|
c2496aab6b | ||
|
|
00734d143e | ||
|
|
d90b3c23ef | ||
|
|
035dbffd28 | ||
|
|
e3710289e5 | ||
|
|
99b67b89ac | ||
|
|
e374841857 | ||
|
|
9f2336476f | ||
|
|
63b560d740 | ||
|
|
f54834cbee | ||
|
|
749633e43c | ||
|
|
308c76cd69 | ||
|
|
fc9a261b77 | ||
|
|
d2d860bd3b | ||
|
|
9cb3638354 | ||
|
|
6133276555 | ||
|
|
a7a3016ae5 | ||
|
|
dd0dcad8c4 | ||
|
|
ed8c9448de | ||
|
|
5e80fb26f0 | ||
|
|
292855332c | ||
|
|
68b0dd83f2 | ||
|
|
611fc61c12 | ||
|
|
2cd4a2c730 | ||
|
|
3e89915a98 | ||
|
|
f4aaee8bd7 | ||
|
|
d56413a289 | ||
|
|
27b629af3a | ||
|
|
1a3cb65fa4 | ||
|
|
4b3eb0e6ec | ||
|
|
f89b13bc62 | ||
|
|
8b7fec5049 | ||
|
|
3a6573f89e | ||
|
|
4e04f5b606 | ||
|
|
39a86e7d52 | ||
|
|
8cfc1c08ec | ||
|
|
c90637398d | ||
|
|
737aabb620 | ||
|
|
0e930b3d63 | ||
|
|
876f626e7d | ||
|
|
0dc704be9c | ||
|
|
e437832698 | ||
|
|
274ca4cb73 | ||
|
|
a99ff646a0 | ||
|
|
02eb26f135 | ||
|
|
e90f19f8ab | ||
|
|
00c2c5cf6e | ||
|
|
3386774f5f | ||
|
|
7e4330be93 | ||
|
|
f5b3e6da93 | ||
|
|
5c39374915 | ||
|
|
4fdb931141 | ||
|
|
3a02854192 | ||
|
|
63c071d6ea | ||
|
|
6dc96f4224 | ||
|
|
aa8f8f7fda | ||
|
|
f5167bb279 | ||
|
|
b84eafc173 | ||
|
|
50ed1b0cf9 | ||
|
|
1d873be184 | ||
|
|
5a0d6eddb1 | ||
|
|
31b45525ce | ||
|
|
430c73b903 | ||
|
|
cc1befbc57 | ||
|
|
68a0ad4112 | ||
|
|
9aca187559 | ||
|
|
d0fb9f3637 | ||
|
|
51d1bc32e8 | ||
|
|
e9b9dd6fa3 | ||
|
|
7e43aa4e12 | ||
|
|
de509db7be | ||
|
|
69c6676d8f | ||
|
|
2ab11dccfc | ||
|
|
d64b4677b9 | ||
|
|
f040f098dc | ||
|
|
abe3391cce | ||
|
|
6ae27c87f6 | ||
|
|
3d2446d235 | ||
|
|
4f01b3157f | ||
|
|
14e728aa0d | ||
|
|
4a266a44db | ||
|
|
900db0d3a3 | ||
|
|
8b95b141b2 | ||
|
|
5a9f2f3181 | ||
|
|
4f43f01e55 | ||
|
|
7221be5a8a | ||
|
|
83ab099b84 | ||
|
|
5f79860c8c | ||
|
|
e4f7b5d181 | ||
|
|
9619a0f226 | ||
|
|
23f414c384 | ||
|
|
4872be0a84 | ||
|
|
691307a269 | ||
|
|
6d846ad2a9 | ||
|
|
ff24c757b7 | ||
|
|
ee4663aa19 | ||
|
|
119c591500 | ||
|
|
275412e902 | ||
|
|
fe75ebe4b2 | ||
|
|
fabb80cf19 | ||
|
|
7577f20c61 | ||
|
|
f441714f93 | ||
|
|
2c9547e6a4 | ||
|
|
3bf3ed03f5 | ||
|
|
c279253e20 | ||
|
|
fa9ddf48d5 | ||
|
|
b8a9c4d1e5 | ||
|
|
b4365c100f | ||
|
|
ef4b0c9cff | ||
|
|
b625b4a076 | ||
|
|
acbe9ce23d | ||
|
|
90c95866d1 | ||
|
|
57fc177266 | ||
|
|
0366f4087f | ||
|
|
3f6d287b44 | ||
|
|
36aa701b56 | ||
|
|
fc5a776645 | ||
|
|
c2b4390bfa | ||
|
|
b371389c27 | ||
|
|
87640ca54a | ||
|
|
8322b03d63 | ||
|
|
594c3580f2 | ||
|
|
0d80c492f1 | ||
|
|
1a29cca1ca | ||
|
|
b54191f29f | ||
|
|
422e4e4785 | ||
|
|
4187cc1f61 | ||
|
|
ede9e45211 | ||
|
|
a36550d94b | ||
|
|
7c3870f3fa | ||
|
|
7ca2796774 | ||
|
|
170cc3bba4 | ||
|
|
1980912ebe | ||
|
|
1571859d67 | ||
|
|
eb4c20a6aa | ||
|
|
1154139b91 | ||
|
|
28e22d7dd2 | ||
|
|
9cfbbb541a | ||
|
|
21bce1cb92 | ||
|
|
fe045343ee | ||
|
|
df017f9267 | ||
|
|
ae5aad178d | ||
|
|
032160a85e | ||
|
|
ecd23e86ce | ||
|
|
51ae782135 | ||
|
|
54b35c30da | ||
|
|
dfe04c5a58 | ||
|
|
4423d472da | ||
|
|
c803a182be | ||
|
|
bc73505e35 | ||
|
|
0a28c818ad | ||
|
|
ce2dcbdbb3 | ||
|
|
1ebe2fcd1a | ||
|
|
c7e9ee1c61 | ||
|
|
51c86795af | ||
|
|
8dffd60f0b | ||
|
|
6bf9b64778 | ||
|
|
26ec7fa346 | ||
|
|
60bd118a9c | ||
|
|
b69507f7f3 | ||
|
|
7d59df0f86 | ||
|
|
9c0272382f | ||
|
|
2de8d9f0f3 | ||
|
|
d0905c02dd | ||
|
|
51fc86f950 | ||
|
|
76bda12760 | ||
|
|
a84b76e56a | ||
|
|
c2018717b6 | ||
|
|
f388513145 | ||
|
|
c16ebe1707 | ||
|
|
b54365c199 | ||
|
|
51d1cc7a96 | ||
|
|
c06141c871 | ||
|
|
eab3fde3af | ||
|
|
de7781b7f9 | ||
|
|
611de03e01 | ||
|
|
de722332b1 | ||
|
|
438ca437ec | ||
|
|
e21e1326b7 | ||
|
|
37e12b4024 | ||
|
|
e126ee5495 | ||
|
|
a308f3f22a | ||
|
|
c5f4cce3ae | ||
|
|
ced8686d11 | ||
|
|
76f6b725b8 | ||
|
|
f9e1dd4bec | ||
|
|
f4f393e5de | ||
|
|
2db5dda266 | ||
|
|
8cf9c59957 | ||
|
|
088556193d | ||
|
|
18d3ab3d15 | ||
|
|
dc6faa33bb | ||
|
|
0d22ae2c1a | ||
|
|
362d982906 | ||
|
|
1006dd9379 | ||
|
|
369316556a | ||
|
|
cf4b29de4b | ||
|
|
09ca7920ea | ||
|
|
9994e033b2 | ||
|
|
dd56f2b47f | ||
|
|
4df043a91c | ||
|
|
d020de4b3d | ||
|
|
dd42f35db0 | ||
|
|
a67af9455b | ||
|
|
d729c82f84 | ||
|
|
44e218194b | ||
|
|
bf1e37f149 | ||
|
|
aee56c388f | ||
|
|
fd5a10bee7 | ||
|
|
b20e890f15 | ||
|
|
4f9530eec7 | ||
|
|
615b60bd37 | ||
|
|
e61afcd109 | ||
|
|
6ac5446940 | ||
|
|
0706681180 | ||
|
|
e44d70b41d | ||
|
|
4bf810cb8f | ||
|
|
c791db4c52 | ||
|
|
e86f3cc594 | ||
|
|
be6243c446 | ||
|
|
4263ee52f3 | ||
|
|
d6f1c91b9c | ||
|
|
105dc4a249 | ||
|
|
aa80c8d0b2 | ||
|
|
7c9bdfb96e | ||
|
|
84b3c0ad31 | ||
|
|
f2538689e7 | ||
|
|
66eb7735dd | ||
|
|
33edb7ea15 | ||
|
|
f3c9be07c0 | ||
|
|
0c131f11f8 | ||
|
|
dc86c9305c | ||
|
|
36ff99882f | ||
|
|
3ed4b1c132 | ||
|
|
8fad2c5127 | ||
|
|
a25749f087 | ||
|
|
248b1ef947 | ||
|
|
feef4bf508 | ||
|
|
aa70ff13f4 | ||
|
|
8a6c64095d | ||
|
|
ea40ffef06 | ||
|
|
44d9dc7440 | ||
|
|
fedb9812bd | ||
|
|
febbee347b | ||
|
|
a7edbd19ad | ||
|
|
c39b2fe03d | ||
|
|
1e23f94b36 | ||
|
|
106a480dad | ||
|
|
587e6fbd8a | ||
|
|
dc2275099a | ||
|
|
bcb9175aa8 | ||
|
|
dd71de9aa1 | ||
|
|
1c4fe6e406 | ||
|
|
697757ba8e | ||
|
|
774df36f41 | ||
|
|
a092b68f61 | ||
|
|
2587b0a8ad | ||
|
|
ff0e849730 | ||
|
|
c25d30ae88 | ||
|
|
51bc70a11b | ||
|
|
d26e54fd89 | ||
|
|
fc6bcc2f5b | ||
|
|
96f0ea2311 | ||
|
|
261f4a4e5b | ||
|
|
f1e933e7aa | ||
|
|
590f001f17 | ||
|
|
88e17c8f86 | ||
|
|
009470883e | ||
|
|
99ad89211a | ||
|
|
67841f8e27 | ||
|
|
eb5ed1490c | ||
|
|
46182f03b3 | ||
|
|
e37d1444c4 | ||
|
|
4b1577ab70 | ||
|
|
c235239ee7 | ||
|
|
f36298c542 | ||
|
|
c3afa55738 | ||
|
|
5c252fd083 | ||
|
|
dc195536d0 | ||
|
|
4d2bbac674 | ||
|
|
eca8914760 | ||
|
|
2aa30edb88 | ||
|
|
4f661aaa69 | ||
|
|
60cfa470b5 | ||
|
|
7a41b74ac1 | ||
|
|
b400648e56 | ||
|
|
d4bb12ec6d | ||
|
|
6fe55a3b48 | ||
|
|
3e72e40af2 | ||
|
|
2501d41d93 | ||
|
|
629f89d95b | ||
|
|
bb00587bd7 | ||
|
|
973886b683 | ||
|
|
4e30bc37f1 | ||
|
|
01c2377de0 | ||
|
|
0fab37c089 | ||
|
|
f7fd209f29 | ||
|
|
76a116641f | ||
|
|
1e0f7d8437 | ||
|
|
7295ec661f | ||
|
|
1a1aac7d09 | ||
|
|
ab2c2e30cb | ||
|
|
65b93f0822 | ||
|
|
da77fb1c3f | ||
|
|
d9ccb11092 | ||
|
|
e62f9b2b48 | ||
|
|
62d17155ec | ||
|
|
702d5bdc01 | ||
|
|
8ee08050cc | ||
|
|
1d81474d1b | ||
|
|
00e9b347db | ||
|
|
72fa369fc9 | ||
|
|
08abff1cae | ||
|
|
f476259bbf | ||
|
|
c87f091a44 | ||
|
|
d165899870 | ||
|
|
6c97600174 | ||
|
|
3e2f7456c4 | ||
|
|
ca9503e4c0 | ||
|
|
dc72a36cb1 | ||
|
|
18e2024e3f | ||
|
|
7d83e209c8 | ||
|
|
1bbfa4984d | ||
|
|
557dee06f0 | ||
|
|
c0bab69cd1 | ||
|
|
f480daf7dc | ||
|
|
cf56d808f2 | ||
|
|
b78e2c7ded | ||
|
|
9b3bcca15e | ||
|
|
f7f25a8815 | ||
|
|
94809ee396 | ||
|
|
851855d0ee | ||
|
|
2a6a0d2997 | ||
|
|
c6516af31e | ||
|
|
05098c68f6 | ||
|
|
a1dafcf45a | ||
|
|
0ee8ee80e1 | ||
|
|
436112252d | ||
|
|
8bd9b94d0a | ||
|
|
504f0dc26f | ||
|
|
c12402ee49 | ||
|
|
f09b3c2f72 | ||
|
|
90c1f6a8c9 | ||
|
|
844c169bdc | ||
|
|
c1930833eb | ||
|
|
f194594e5b | ||
|
|
5bb4adea30 | ||
|
|
376b83050a | ||
|
|
bee87395b1 | ||
|
|
21ee90ae41 | ||
|
|
d2f00d832e | ||
|
|
764064716f | ||
|
|
7b051ddc22 | ||
|
|
9aac96313a | ||
|
|
fcdb340623 | ||
|
|
4301eb6553 | ||
|
|
649bbc7c4f | ||
|
|
1eb6145080 | ||
|
|
00f6f39b1c | ||
|
|
d0f5c2c7ab | ||
|
|
1cfb83bca9 | ||
|
|
4ef705ae0f | ||
|
|
5e6f6a1c50 | ||
|
|
59c36ee972 | ||
|
|
ae3cfad10b | ||
|
|
21010b000e | ||
|
|
4d2b00f612 | ||
|
|
bab8b54ed8 | ||
|
|
4bd5db14b4 | ||
|
|
4a9136040c | ||
|
|
8b97414f3d | ||
|
|
1e8e9ecc98 | ||
|
|
2d8ab9ff5d | ||
|
|
17f66331ea | ||
|
|
6bf30bc6b5 | ||
|
|
f798777a3b | ||
|
|
6207121c03 | ||
|
|
248994dab6 | ||
|
|
a68db4f0db | ||
|
|
aad6b676b0 | ||
|
|
bf91b2045c | ||
|
|
fcf6ec6731 | ||
|
|
b377040144 | ||
|
|
c1328d9619 | ||
|
|
f918edd846 | ||
|
|
6b49cd7d28 | ||
|
|
afa3aa2232 | ||
|
|
1c59a41cc5 | ||
|
|
0626b22c70 | ||
|
|
fbbec507d1 | ||
|
|
a4b0416174 | ||
|
|
659f33dc55 | ||
|
|
20ddf553ce | ||
|
|
7483de5e90 | ||
|
|
9f1d6258a2 | ||
|
|
99b59a90b6 | ||
|
|
56bf9bad25 | ||
|
|
229b6a262e | ||
|
|
74d9fb863f | ||
|
|
e332fb505c | ||
|
|
dafde586ec | ||
|
|
cb101e4dbe | ||
|
|
6fdfee36fe | ||
|
|
0787301ddb | ||
|
|
2af510a3ee | ||
|
|
fdeca2c026 | ||
|
|
23fd15f840 | ||
|
|
06b7d302a2 | ||
|
|
b70db9dc03 | ||
|
|
d4ac69d88e | ||
|
|
59c2295dfd | ||
|
|
4eb9a09385 | ||
|
|
db2d7c8c50 | ||
|
|
2ebf9d3d00 | ||
|
|
67de14a3b8 | ||
|
|
a40c88ebf3 | ||
|
|
23129da3e2 | ||
|
|
59402bca7b | ||
|
|
c3405095b2 | ||
|
|
2181418cc5 | ||
|
|
e0235ed190 | ||
|
|
02e41baa47 | ||
|
|
91ef68992c | ||
|
|
43964ff7a2 | ||
|
|
19c4acf391 | ||
|
|
ed502949dd | ||
|
|
a0c259ffbc | ||
|
|
d6d66faae3 | ||
|
|
15c84fcc94 | ||
|
|
1438f06c12 | ||
|
|
ca5bb2170c | ||
|
|
05a2fd97f8 | ||
|
|
dedd51df91 | ||
|
|
290676e4d1 | ||
|
|
8725ab4caa | ||
|
|
3891f90f43 | ||
|
|
9f17ba5ae4 | ||
|
|
81d42cb3b9 | ||
|
|
dfef9f470f | ||
|
|
f5da417450 | ||
|
|
a888083c50 | ||
|
|
99cfc4fbce | ||
|
|
fcceeed9fa | ||
|
|
4cf0e46c38 | ||
|
|
34eff2a2f9 | ||
|
|
e82cb2c7ba | ||
|
|
0711093ccd | ||
|
|
15d0006841 | ||
|
|
282b949c24 | ||
|
|
005dbf3aa8 | ||
|
|
a1dcba4731 | ||
|
|
2a62beeb5f | ||
|
|
242fa8afb2 | ||
|
|
e3ed722252 | ||
|
|
9a16dc28b7 | ||
|
|
de86809b69 | ||
|
|
9420bfde5b | ||
|
|
adb460b644 | ||
|
|
06b47a5792 | ||
|
|
ca2dd2d476 | ||
|
|
60f82d2a55 | ||
|
|
414ff503ef | ||
|
|
4ec5766ea9 | ||
|
|
b7c26c43ca | ||
|
|
4b2ed52f44 | ||
|
|
f381c92f0b | ||
|
|
3a303cc8fb | ||
|
|
09724cfa71 | ||
|
|
d74c2a6e3f | ||
|
|
0f9352db3b | ||
|
|
afec420ce6 | ||
|
|
d5e3ad9da0 | ||
|
|
0f103ed2a4 | ||
|
|
d62d6a1f27 | ||
|
|
a4ad5d68a9 | ||
|
|
30c0fd479e | ||
|
|
756966c55b | ||
|
|
288e092d2e | ||
|
|
72745cd8fe | ||
|
|
8060e82745 | ||
|
|
373713f7e0 | ||
|
|
66401b42d8 | ||
|
|
2d5943b21a | ||
|
|
920b519ebf | ||
|
|
bf1c02d328 | ||
|
|
6e9023e090 | ||
|
|
1b7a26d932 | ||
|
|
cb4b63f8b3 | ||
|
|
8eb15a924f | ||
|
|
6a610a9d51 | ||
|
|
821a893f70 | ||
|
|
afcd80de37 | ||
|
|
edc327ba33 | ||
|
|
90e8cc86c2 | ||
|
|
9ab7c39d56 | ||
|
|
207bac9452 | ||
|
|
199562fd05 | ||
|
|
3a969a83b7 | ||
|
|
aab1ee9edc | ||
|
|
080c75efe6 | ||
|
|
7e6dadb508 | ||
|
|
19c3f2cb04 | ||
|
|
842f14af4c | ||
|
|
05c258026a | ||
|
|
1bb38911dc | ||
|
|
ec943fffdc | ||
|
|
ae1d182b30 | ||
|
|
82c056b955 | ||
|
|
7acc2aa383 | ||
|
|
4e6a39ed11 | ||
|
|
d587c6b10e | ||
|
|
51c5a05ea7 | ||
|
|
f0cac8c5d3 | ||
|
|
76f3430c68 | ||
|
|
0e4f7082b0 | ||
|
|
66f005f275 | ||
|
|
bc10d500b7 | ||
|
|
7c7f0fdae3 | ||
|
|
4fa901c017 | ||
|
|
fd11c37825 | ||
|
|
f1b82dbf1f | ||
|
|
8891455e10 | ||
|
|
5540f25932 | ||
|
|
ee9bbbe50b | ||
|
|
e013c90993 | ||
|
|
fa85be4b94 | ||
|
|
276cba08ee | ||
|
|
0e312c88c1 | ||
|
|
00301e3642 | ||
|
|
a2be4b7b5e | ||
|
|
b5a509f27f | ||
|
|
6b722a14c8 | ||
|
|
dd0c805b09 | ||
|
|
6c35490cfb | ||
|
|
19a04ea804 | ||
|
|
a9a63914b2 | ||
|
|
b7d1c3f5f6 | ||
|
|
a8ccdbc833 | ||
|
|
decf1cf537 | ||
|
|
e5cfa521da | ||
|
|
dd80627dfa | ||
|
|
f79c844c71 | ||
|
|
cb550dfed0 | ||
|
|
602623a0ba | ||
|
|
6d7646c0a2 | ||
|
|
51518aeb03 | ||
|
|
f3e710c814 | ||
|
|
b132b14982 | ||
|
|
6cd45fa81c | ||
|
|
95e4b8fcdf | ||
|
|
b4cd64e999 | ||
|
|
82dbb93e2c | ||
|
|
b7e12334d6 | ||
|
|
a39eac6f1b | ||
|
|
418ec2a01f | ||
|
|
0380a9ce33 | ||
|
|
ed338d1455 | ||
|
|
10699314d4 | ||
|
|
1f7b6133cd | ||
|
|
3c0ed4d5e3 | ||
|
|
e38f0824dc | ||
|
|
a7d2c50550 | ||
|
|
9fee276214 | ||
|
|
ef7c7d879b | ||
|
|
ea64444c8b | ||
|
|
84308f3518 | ||
|
|
88bcdbadce | ||
|
|
b50d1384dc | ||
|
|
b3457271a9 | ||
|
|
fc6de7e86c | ||
|
|
6b87bc3fa9 | ||
|
|
238df12920 | ||
|
|
f7f32f2f98 | ||
|
|
1f8ac0ff23 | ||
|
|
75ff3efb59 | ||
|
|
f0f9efa277 | ||
|
|
4ab2ed10f5 | ||
|
|
53031ad8d4 | ||
|
|
ce1ad010e9 | ||
|
|
5d6dbe1fc3 | ||
|
|
774504e209 | ||
|
|
2275cd5985 | ||
|
|
3b520d6e93 | ||
|
|
80e1fee208 | ||
|
|
011d6ba71b | ||
|
|
9296d95084 | ||
|
|
c6463831ac | ||
|
|
247ac10215 | ||
|
|
678be9902a | ||
|
|
bafd578866 | ||
|
|
8ac2dc2e9e | ||
|
|
5689bf621c | ||
|
|
ae1cf53dcf | ||
|
|
ced019291b | ||
|
|
b483409c63 | ||
|
|
1fe6f2eff9 | ||
|
|
07f8c327e4 | ||
|
|
e9fa98edfb | ||
|
|
6c102f09f2 | ||
|
|
ea9d06c819 | ||
|
|
05fb8b75ef | ||
|
|
ee2aa9ce50 | ||
|
|
8b1bf8d4f9 | ||
|
|
ca9560effb | ||
|
|
6b64a8a9c6 | ||
|
|
dd3c1ac3cf | ||
|
|
16c8911dfd | ||
|
|
8ea9ba8860 | ||
|
|
1d8decfdad | ||
|
|
57431ce6c2 | ||
|
|
01723e0d12 | ||
|
|
8733a24ebf | ||
|
|
58b4ecc0aa | ||
|
|
b43b8439c4 | ||
|
|
f2747ef4e7 | ||
|
|
e3d8c71f97 | ||
|
|
0ace5cf477 | ||
|
|
6364ac9ac7 | ||
|
|
e89daadfcf | ||
|
|
eee737186f | ||
|
|
c5b54ec27e | ||
|
|
2ad43789cc | ||
|
|
38a90fa01e | ||
|
|
037fa65c63 | ||
|
|
de68594fd6 | ||
|
|
6327f51f5b | ||
|
|
781ee82b9d | ||
|
|
34d02466d9 | ||
|
|
8dd34d9ea9 | ||
|
|
3c0bad9ea2 | ||
|
|
2bf11ffde1 | ||
|
|
bb9cb739c6 | ||
|
|
cc8d637715 | ||
|
|
eafc9ad78c | ||
|
|
c1719c8f1a | ||
|
|
d3fb567fdb | ||
|
|
8b1e5aa320 | ||
|
|
37e654faa0 | ||
|
|
7c73b5e3df | ||
|
|
6a9661b374 | ||
|
|
6429164852 | ||
|
|
fa68cbbe4e | ||
|
|
f323690049 | ||
|
|
02ffff01d5 | ||
|
|
2cdeb85cd6 | ||
|
|
6ea7c48ae9 | ||
|
|
65682aa60d | ||
|
|
bb1737daec | ||
|
|
fef494949f | ||
|
|
0a15d488c8 | ||
|
|
e69eb46911 | ||
|
|
8f8db3f542 | ||
|
|
8923704f3c | ||
|
|
ba2d122308 | ||
|
|
938bea9910 | ||
|
|
8719c7a2db | ||
|
|
16fa12f455 | ||
|
|
555b1c80e3 | ||
|
|
a12a5f387a | ||
|
|
c087e33b86 | ||
|
|
49c468f00a | ||
|
|
b7f79f0adc | ||
|
|
2756a106a9 | ||
|
|
efffa0def2 | ||
|
|
6beb1c135c | ||
|
|
24ab2e85e5 | ||
|
|
c6749560b5 | ||
|
|
a0160b8e47 | ||
|
|
2f9df8c8e2 | ||
|
|
0076f1251c | ||
|
|
fe911a7b7a | ||
|
|
bdd79a9984 | ||
|
|
513f43f465 | ||
|
|
f5116cddb4 | ||
|
|
bbe10004b4 | ||
|
|
e9252a9ee3 | ||
|
|
875b0739aa | ||
|
|
7f56115abc | ||
|
|
5a29f19faa | ||
|
|
ae45e158a3 | ||
|
|
81c44528ba | ||
|
|
9cfb6baaf2 | ||
|
|
1de902fe8d | ||
|
|
7ac141ba08 | ||
|
|
fd7838ef2c | ||
|
|
7ffb1de1c9 | ||
|
|
e4daa5a924 | ||
|
|
147022234e | ||
|
|
2054708c53 | ||
|
|
3fe3cf71ab | ||
|
|
7787885463 | ||
|
|
f320980006 | ||
|
|
7e0a3c114d | ||
|
|
a86d7d27c1 | ||
|
|
b00cf65732 | ||
|
|
69364b2183 | ||
|
|
6b3f175885 | ||
|
|
cb7aa561cf | ||
|
|
253869ad46 | ||
|
|
109da32164 | ||
|
|
b69c0ac746 | ||
|
|
4cc61c038e | ||
|
|
ae725feee9 | ||
|
|
49a57576dd | ||
|
|
b94b9ad2d8 | ||
|
|
b28e416d0c | ||
|
|
e13f4a7f29 | ||
|
|
f96cbea521 | ||
|
|
d4fb9be250 | ||
|
|
41cdb6bbaf | ||
|
|
f43b6f04dc | ||
|
|
791b785dea | ||
|
|
d7849c793c | ||
|
|
5568372bb9 | ||
|
|
cba26c92f5 | ||
|
|
47de5118f2 | ||
|
|
81f3acfa38 | ||
|
|
aac48fc308 | ||
|
|
5635d6c862 | ||
|
|
e9d0ac5110 | ||
|
|
7b36c8ab54 | ||
|
|
e04e5e0185 | ||
|
|
67d3b5fb82 | ||
|
|
c31a0e37bf | ||
|
|
ab01ce41ed | ||
|
|
1e6b8a0be0 | ||
|
|
dba0de88f3 | ||
|
|
0cdbb710d2 | ||
|
|
75f024cb19 | ||
|
|
32e4e0d835 | ||
|
|
7c85a511a2 | ||
|
|
6be92f92fb | ||
|
|
7277d00e1a | ||
|
|
29eb3dd384 | ||
|
|
501ec25f5a | ||
|
|
6ae0c0bd22 | ||
|
|
d9c1b104cd | ||
|
|
fbf4284866 | ||
|
|
06126544bd | ||
|
|
e9c93305b9 | ||
|
|
3197156aa1 | ||
|
|
f377292ffe | ||
|
|
ca6a60b534 | ||
|
|
99755624e8 | ||
|
|
96e4442181 | ||
|
|
bc5eb3cf57 | ||
|
|
afb032f8f9 | ||
|
|
c86a615713 | ||
|
|
c494add2ce | ||
|
|
f94b1e70fc | ||
|
|
42ed11a5cb | ||
|
|
a7c6c43112 | ||
|
|
17d6def081 | ||
|
|
73e7aeca6e | ||
|
|
305f3f6abe | ||
|
|
e4d6a72087 | ||
|
|
85e5970d6e | ||
|
|
424e7406af | ||
|
|
3e148b629d | ||
|
|
545df755b8 | ||
|
|
e6cb439d9c | ||
|
|
879b840ee2 | ||
|
|
773fad9701 | ||
|
|
fbc3334e8c | ||
|
|
0baae51425 | ||
|
|
ad7df9f7d1 | ||
|
|
460fbbacc7 | ||
|
|
d67238d46f | ||
|
|
5ec1ee086d | ||
|
|
c0f1e408da | ||
|
|
0a63dd104d | ||
|
|
d34f297b37 | ||
|
|
e0591500ce | ||
|
|
c43193a0c8 | ||
|
|
0896e7164a | ||
|
|
070819754f | ||
|
|
56d7dee25d | ||
|
|
9d21bcacb6 | ||
|
|
d0781c42dc | ||
|
|
00567645d0 | ||
|
|
92b9d68863 | ||
|
|
1f2d2bff71 | ||
|
|
3bdb380a1a | ||
|
|
1ac2215724 | ||
|
|
9d92f21638 | ||
|
|
9838a7cb6d | ||
|
|
0d709ea8db | ||
|
|
9a21cb9cc7 | ||
|
|
6424f45c19 | ||
|
|
51b1dbd2af | ||
|
|
9a8df262d9 | ||
|
|
9c2df74e54 | ||
|
|
581f671643 | ||
|
|
b99da0c805 | ||
|
|
8a961bfa21 | ||
|
|
cd86d57763 | ||
|
|
2753b468fd | ||
|
|
be1915d2d7 | ||
|
|
42ef46b74e | ||
|
|
79d0e74056 | ||
|
|
8de4524428 | ||
|
|
8f71f965b9 | ||
|
|
0e3641bba2 | ||
|
|
e3aa495e0b | ||
|
|
0edee37498 | ||
|
|
7968ed6d69 | ||
|
|
44edba6f75 | ||
|
|
0c7e95539f | ||
|
|
8938990c04 | ||
|
|
57a07a498f | ||
|
|
136bc7ac09 | ||
|
|
a1efcefdce | ||
|
|
5dbf05c31d | ||
|
|
af9612e98e | ||
|
|
b31a893caf | ||
|
|
c8bc192e0b | ||
|
|
def2b35e6e | ||
|
|
14b1b7c862 | ||
|
|
c8b90df6f1 | ||
|
|
d0bbfca831 | ||
|
|
fe5d037600 | ||
|
|
5fe94c4e2b | ||
|
|
1d8310ed44 | ||
|
|
c79f8c89d7 | ||
|
|
7c0c3211d1 | ||
|
|
0ef98f0558 | ||
|
|
94bee9e882 | ||
|
|
da135d9958 | ||
|
|
206a16f9e3 | ||
|
|
e048859afd | ||
|
|
8c081c50d4 | ||
|
|
0dc7dfcb50 | ||
|
|
c9b61ef010 | ||
|
|
59fef0c1b2 | ||
|
|
23d8a279c4 | ||
|
|
349039ff5d | ||
|
|
a72155a57f | ||
|
|
41f8663dcd | ||
|
|
84007cb1ec | ||
|
|
265c51b8aa | ||
|
|
f50ab87b53 | ||
|
|
4c5d041c21 | ||
|
|
df9a06ddfe | ||
|
|
55aaf33d94 | ||
|
|
8ea339139e | ||
|
|
e950e7e001 | ||
|
|
68339f3acb | ||
|
|
f4660f0250 | ||
|
|
bf3cda54ba | ||
|
|
1b4194373b | ||
|
|
241093bb96 | ||
|
|
ccef7bd1bc | ||
|
|
e2db152c6c | ||
|
|
5c490e999d | ||
|
|
a11e1527f0 | ||
|
|
6ac11a554a | ||
|
|
42dd8d1d9d | ||
|
|
722f00e485 | ||
|
|
67c328f4d0 | ||
|
|
146e61ed03 | ||
|
|
6718a32fe4 | ||
|
|
2af6f4cdf6 | ||
|
|
60bdd3eccd | ||
|
|
c1b93179ff | ||
|
|
7387f675e2 | ||
|
|
f0a43e0402 | ||
|
|
c6b0820438 | ||
|
|
f86c46e160 | ||
|
|
eed0c9d5b0 | ||
|
|
e38a27d93d | ||
|
|
b0904c2e99 | ||
|
|
bec5fe85cc | ||
|
|
5bc4e678bf | ||
|
|
90e7343fb5 | ||
|
|
5c0d67dc50 | ||
|
|
b6512bcbb6 | ||
|
|
097e6d5340 | ||
|
|
a45a537cdb | ||
|
|
0467e5c1d5 | ||
|
|
5c1fa6d52c | ||
|
|
337459feb0 | ||
|
|
590e2d18f7 | ||
|
|
ef36b454ba | ||
|
|
4465810ff8 | ||
|
|
d301bc60b7 | ||
|
|
6307a3212b | ||
|
|
92a082b0f7 | ||
|
|
15627e7803 | ||
|
|
a70ce9cef7 | ||
|
|
eab6f0d713 | ||
|
|
cc96f85ec7 | ||
|
|
6557197bae | ||
|
|
ed351938d4 | ||
|
|
718cf234b8 | ||
|
|
159b2d50eb | ||
|
|
bc8aebeffe | ||
|
|
135eab037e | ||
|
|
714aa59a34 | ||
|
|
33fe2bcff4 | ||
|
|
665b921d63 | ||
|
|
b99634764f | ||
|
|
4a8cd180f8 | ||
|
|
6c7d3e62dd | ||
|
|
c4b5476088 | ||
|
|
61835e9f08 | ||
|
|
d49b011d65 | ||
|
|
f881bbb137 | ||
|
|
f55d56bf4a | ||
|
|
ffc49d96b3 | ||
|
|
8a40dda3ab | ||
|
|
ab0682917a | ||
|
|
ed15927d90 | ||
|
|
e43cf81c38 | ||
|
|
eb08a9f91f | ||
|
|
9423ff5e48 | ||
|
|
df580fcb39 | ||
|
|
ca58911cc2 | ||
|
|
6afc48849a | ||
|
|
5569b114ba | ||
|
|
c8bfe780c7 | ||
|
|
b79a2ccf7c | ||
|
|
27d0c58319 | ||
|
|
ffadca7f68 | ||
|
|
62c086a725 | ||
|
|
99781443b0 | ||
|
|
f498cb3918 | ||
|
|
e8482ab9e9 | ||
|
|
868ff9ed2b | ||
|
|
42acf8dcce | ||
|
|
1c7e7048a8 | ||
|
|
73e4d3144b | ||
|
|
1a633adde6 | ||
|
|
09bd51f481 | ||
|
|
084c3114f4 | ||
|
|
47639340ec | ||
|
|
fd14a5794e | ||
|
|
a4447fa606 | ||
|
|
f18d7310a8 | ||
|
|
28251f8b92 | ||
|
|
50b54580de | ||
|
|
d88883328a | ||
|
|
ca6687d428 | ||
|
|
848d83c496 | ||
|
|
5bd73fc10d | ||
|
|
40e548ebf0 | ||
|
|
64b0e69430 | ||
|
|
daec673b81 | ||
|
|
01d6bdb497 | ||
|
|
216fce70aa | ||
|
|
5939ce8cc6 | ||
|
|
9471ed2d09 | ||
|
|
0d31e955ae | ||
|
|
5697bb2915 | ||
|
|
ca7b27de3f | ||
|
|
867468ee66 | ||
|
|
4b4a4ad592 | ||
|
|
b341e52214 | ||
|
|
1e8e7b948e | ||
|
|
1bc13e94f7 | ||
|
|
9db87132b1 | ||
|
|
9992855cb8 | ||
|
|
23dce42a94 | ||
|
|
edf92303ae | ||
|
|
719cd75f64 | ||
|
|
ceb9973657 | ||
|
|
cf82cf996e | ||
|
|
5c702738cf | ||
|
|
485b2271f0 | ||
|
|
dce9409ccc | ||
|
|
d5ce48bd4b | ||
|
|
492dfa84d8 | ||
|
|
e4fddc3596 | ||
|
|
ad9d9ce1a5 | ||
|
|
904a60f04a | ||
|
|
bf3b4bfca7 | ||
|
|
560ee45b50 | ||
|
|
d2794114f4 | ||
|
|
e303a45dd1 | ||
|
|
c47bc74ad0 | ||
|
|
a728822993 | ||
|
|
5cd4d47f9e | ||
|
|
2832e7c45e | ||
|
|
04bb45947d | ||
|
|
f2c9b67854 | ||
|
|
e05ae08b77 | ||
|
|
1ab1fd4c50 | ||
|
|
d17dd65d0f | ||
|
|
a832bb641b | ||
|
|
18da164130 | ||
|
|
07b1a144b3 | ||
|
|
4c3f304ba5 | ||
|
|
c7445def21 | ||
|
|
d23f6690af | ||
|
|
0a8264c9c8 | ||
|
|
8c7cda84e1 | ||
|
|
6767a07a8d | ||
|
|
6908c2ab65 | ||
|
|
80b65619bf | ||
|
|
2aa87dd069 | ||
|
|
bb99d269eb | ||
|
|
84a1f2a771 | ||
|
|
e67838932d | ||
|
|
50e4d6db6c | ||
|
|
bcf070cb73 | ||
|
|
4ce96634c6 | ||
|
|
c356754aaa | ||
|
|
75a130bafd | ||
|
|
1d6e51d14a | ||
|
|
da9432b5b2 | ||
|
|
d4971ef2da | ||
|
|
bdb7f80571 | ||
|
|
c7299f4daf | ||
|
|
659224a207 | ||
|
|
2c5214724b | ||
|
|
b2e3f84156 | ||
|
|
a58649faa6 | ||
|
|
a4883507b5 | ||
|
|
461e272d79 | ||
|
|
981004eec4 | ||
|
|
0332362598 | ||
|
|
f519b271d2 | ||
|
|
be7a4f781f | ||
|
|
0e3c815e9b | ||
|
|
3b4147f90e | ||
|
|
3b10cc3097 | ||
|
|
d8ec87d0e1 | ||
|
|
cdd38dd55e | ||
|
|
236a7cf3fb | ||
|
|
3e74b38a95 | ||
|
|
a05acadf80 | ||
|
|
c5d1f380d2 | ||
|
|
9883b4e236 | ||
|
|
348187d2f4 | ||
|
|
86ee66d21d | ||
|
|
2198b4820a | ||
|
|
4b7b9e4362 | ||
|
|
70f2bbf4f8 | ||
|
|
7a3efb9981 | ||
|
|
e1954b1df9 | ||
|
|
37c2ce53d7 | ||
|
|
9d3773e58a | ||
|
|
aa651973fc | ||
|
|
745852ef2e | ||
|
|
ca5ad85bbd | ||
|
|
8296093beb | ||
|
|
6d79fe5f4c | ||
|
|
ef6369d71e | ||
|
|
7616799adb | ||
|
|
dea3513125 | ||
|
|
5f4645d505 | ||
|
|
c439cc03a2 | ||
|
|
2603bbfcd6 | ||
|
|
dc1d563549 | ||
|
|
416a66f68a | ||
|
|
8f2da915aa | ||
|
|
aad74158ba | ||
|
|
98dde187bb | ||
|
|
ab71450c69 | ||
|
|
a4789e2b2c | ||
|
|
1c2829a326 | ||
|
|
9be8cd40d2 | ||
|
|
90a3538806 | ||
|
|
b68e7f3e9e | ||
|
|
4f15fc2123 | ||
|
|
2bc6c15d25 | ||
|
|
a740fb34c5 | ||
|
|
be1416b7b5 | ||
|
|
aad8dc50e7 | ||
|
|
47f6de58c3 | ||
|
|
61c630caf0 | ||
|
|
2985d55edf | ||
|
|
4f794571a2 | ||
|
|
d5ae3da893 | ||
|
|
80d5f42b57 | ||
|
|
d5aa682cbc | ||
|
|
b61557d3c3 | ||
|
|
d4e2622ea8 | ||
|
|
88a97033fb | ||
|
|
c4f9869e7c | ||
|
|
c455a17abe | ||
|
|
9a632134ae | ||
|
|
1b540181a7 | ||
|
|
1f8aa6c262 | ||
|
|
46bea27cb7 | ||
|
|
98bd12241d | ||
|
|
c3410c4b14 | ||
|
|
1e9f9181a1 | ||
|
|
07e9c5bd93 | ||
|
|
e10d21d678 | ||
|
|
e3d9eb7d82 | ||
|
|
97f6e8a07b | ||
|
|
865ef8d447 | ||
|
|
0a342a803c | ||
|
|
120b35a617 | ||
|
|
3f92a546df | ||
|
|
132e39c8fd | ||
|
|
634654578f | ||
|
|
18f95d3d1e | ||
|
|
b38723ece2 | ||
|
|
bf1366412a | ||
|
|
a6f7e05d3d | ||
|
|
660b155eaa | ||
|
|
29e939db7f | ||
|
|
3c2d921300 | ||
|
|
d1e5a9aff3 | ||
|
|
54d4879c69 | ||
|
|
78aa45a970 | ||
|
|
3c33c59aa9 | ||
|
|
b9b4d31c6b | ||
|
|
0501159ac0 | ||
|
|
779b084b53 | ||
|
|
f90f2334ba | ||
|
|
a8c35c6f7e | ||
|
|
ced08aeaf7 | ||
|
|
9c5bc763ac | ||
|
|
dd0039e94c | ||
|
|
9d836c789f | ||
|
|
ef291bb790 | ||
|
|
d4e3cf51b7 | ||
|
|
247123965e | ||
|
|
097a1ac3d3 | ||
|
|
ec532f622f | ||
|
|
d73acd6896 | ||
|
|
0c4e3aa5f1 | ||
|
|
cbd892a4b2 | ||
|
|
e46acbee43 | ||
|
|
c1995c1eea | ||
|
|
423e2932aa | ||
|
|
c0ac0fa183 | ||
|
|
521dec2e04 | ||
|
|
0a47aa59fc | ||
|
|
3a21c9a35b | ||
|
|
6ee1e35329 | ||
|
|
a077972ce5 | ||
|
|
9d89a8dea4 | ||
|
|
d965b65b0a | ||
|
|
bc1593e833 | ||
|
|
de5026b90f | ||
|
|
13f0b5e782 | ||
|
|
6310dde4f5 | ||
|
|
bbf4412ff3 | ||
|
|
1409f236da | ||
|
|
0cca3a12e6 | ||
|
|
5852a9e0ab | ||
|
|
8c4c99bbaf | ||
|
|
69e3627946 | ||
|
|
a27a355071 | ||
|
|
826d8236d9 | ||
|
|
776e436e35 | ||
|
|
af7d3092a5 | ||
|
|
62c597eb3b | ||
|
|
78cb86215b | ||
|
|
8a7f7b8842 | ||
|
|
172db05d8d | ||
|
|
113bebfb4d | ||
|
|
af7a1a1f58 | ||
|
|
0ab6311cf5 | ||
|
|
24069b56dc | ||
|
|
87b7ea14d5 | ||
|
|
7513092432 | ||
|
|
192553aed9 | ||
|
|
d44882fddc | ||
|
|
cede6403e1 | ||
|
|
e25ecea684 | ||
|
|
ac9887afdc | ||
|
|
58c5146592 | ||
|
|
5e06c6d5ad | ||
|
|
ca6c29e463 | ||
|
|
1ac8691199 | ||
|
|
4bb596e2cd | ||
|
|
78a7d4deea | ||
|
|
bed9a74b58 | ||
|
|
5af01bba4e | ||
|
|
9fe9753cbc | ||
|
|
cef5745d2d | ||
|
|
cd1e4bacf8 | ||
|
|
7de8f82295 | ||
|
|
62887a9cc8 | ||
|
|
c0e7a6ecbf | ||
|
|
465a0c3d80 | ||
|
|
4b4cc93ae7 | ||
|
|
4a89a9fa16 | ||
|
|
eb7a9f89e2 | ||
|
|
162041c794 | ||
|
|
0e7ef1637d | ||
|
|
91d5159743 | ||
|
|
efb53d3190 | ||
|
|
c97bb58e3c | ||
|
|
3cecb62705 | ||
|
|
52db01d8ef | ||
|
|
1d500ded67 | ||
|
|
edc5f3fc15 | ||
|
|
54fb03153a | ||
|
|
06d456fc87 | ||
|
|
5573c629b5 | ||
|
|
2cecc17ef0 | ||
|
|
c52ed93bf8 | ||
|
|
84498d5a55 | ||
|
|
8faf3b0e26 | ||
|
|
a7bd494ec3 | ||
|
|
b6f0dc3ba7 | ||
|
|
e2bdab9e2d | ||
|
|
28210ab14d | ||
|
|
593d55ec09 | ||
|
|
5ad7e9a8ca | ||
|
|
7f70fcf679 | ||
|
|
f653942065 | ||
|
|
d4611b829d | ||
|
|
b2656b9cb1 | ||
|
|
2eb82cc1d7 | ||
|
|
0a21cb6d08 | ||
|
|
519aece8a5 | ||
|
|
e6c78facfc | ||
|
|
01ab7758d8 | ||
|
|
957892b677 | ||
|
|
c701a4a344 | ||
|
|
92bf826ec5 | ||
|
|
446384a7f5 | ||
|
|
6d82a11645 | ||
|
|
bd5cabf0ff | ||
|
|
51742366fe | ||
|
|
5341322071 | ||
|
|
23ed2856ce | ||
|
|
84041e0c55 | ||
|
|
4b75ced52c | ||
|
|
61bb01b31d | ||
|
|
64f41d0d0c | ||
|
|
e42e3ca421 | ||
|
|
b89e6d9d93 | ||
|
|
c771328bb1 | ||
|
|
52925a2a46 | ||
|
|
013030041a | ||
|
|
be973bc87e | ||
|
|
617f57e1c9 | ||
|
|
0aacedf943 | ||
|
|
b05e8a5e24 | ||
|
|
8e169f9702 | ||
|
|
32063db46e | ||
|
|
31d67a1af3 | ||
|
|
a9f2f672c7 | ||
|
|
76709892bc | ||
|
|
a38fb16295 | ||
|
|
e334ad6f7e | ||
|
|
40dcc8a7f1 | ||
|
|
97a374c00b | ||
|
|
fe9364c58b | ||
|
|
e9fb4242d5 | ||
|
|
48e44e13c6 | ||
|
|
5e2f98af65 | ||
|
|
3521e129cd | ||
|
|
0b408f4fc0 | ||
|
|
022fdb9cfd | ||
|
|
e4f43683d4 | ||
|
|
514f9964c1 | ||
|
|
2c4927debe | ||
|
|
0f23931fe4 | ||
|
|
98ee9f0979 | ||
|
|
048f05d39c | ||
|
|
b71e5964aa | ||
|
|
9d792352bf | ||
|
|
86c791b8a6 | ||
|
|
3160b5bad1 | ||
|
|
610f886fd8 | ||
|
|
770f8af62b | ||
|
|
6ef9cf273e | ||
|
|
793d1c6a5d | ||
|
|
8f6a12eae4 | ||
|
|
5c0425fb71 | ||
|
|
96098841dd | ||
|
|
b7627208ea | ||
|
|
32aa015d5b | ||
|
|
7af75dfe3c | ||
|
|
1a53b4daea | ||
|
|
5385fb38db | ||
|
|
be11966a64 | ||
|
|
fee737b267 | ||
|
|
64f1bff13f | ||
|
|
96fcfe4d53 | ||
|
|
ce567c481b | ||
|
|
33311714e5 | ||
|
|
1bab4ccdb7 | ||
|
|
d91ebc1049 | ||
|
|
4e48388693 | ||
|
|
8df2d3cc58 | ||
|
|
f6cbc879ef | ||
|
|
46c773aba6 | ||
|
|
cd91edf26c | ||
|
|
8fd55a1d81 | ||
|
|
3908097c54 | ||
|
|
12f18cbed8 | ||
|
|
05a1187e2e | ||
|
|
5effb1a89b | ||
|
|
b14e86bb91 | ||
|
|
c3445a747d | ||
|
|
a5c481cf61 | ||
|
|
fd1936c45f | ||
|
|
fbeb45a1a0 | ||
|
|
23bd3e7cc9 | ||
|
|
7cda8f4123 | ||
|
|
a42e3edf56 | ||
|
|
76933f69b9 | ||
|
|
5c9d26baeb | ||
|
|
9248db971a | ||
|
|
2dcc149fee | ||
|
|
38230fc518 | ||
|
|
8edecffcc0 | ||
|
|
ca05969f8d | ||
|
|
1b504b6fbd | ||
|
|
dfd3d5075d | ||
|
|
c51722a121 | ||
|
|
23ec91dee0 | ||
|
|
ccba159639 | ||
|
|
20cfa0a207 | ||
|
|
835b8a5333 | ||
|
|
decd40bc26 | ||
|
|
2891da25f5 | ||
|
|
c54933bf33 | ||
|
|
820c565d21 | ||
|
|
e5e8c13f23 | ||
|
|
b81206c15d | ||
|
|
31c144261f | ||
|
|
4d0da0a5b2 | ||
|
|
8c96616b51 | ||
|
|
c7b49d9b93 | ||
|
|
7f0d04dba6 | ||
|
|
1707995378 | ||
|
|
f24f82b25b | ||
|
|
391c38057d | ||
|
|
e2e9819c58 | ||
|
|
dc61d132cf | ||
|
|
959f18b67b | ||
|
|
ee75a63057 | ||
|
|
bd035a180e | ||
|
|
8df9033bfc | ||
|
|
3e57716f0e | ||
|
|
c78db66665 | ||
|
|
8dec84b3b2 | ||
|
|
fcf707b1ce | ||
|
|
563ac77b2f | ||
|
|
e091cd6180 | ||
|
|
a71f1f88d9 | ||
|
|
bb670249cf | ||
|
|
f632698568 | ||
|
|
0c81cdf309 | ||
|
|
fbb5296f68 | ||
|
|
14c353993b | ||
|
|
2cdd7c9577 | ||
|
|
4512eeca9a | ||
|
|
2c27db0c85 | ||
|
|
ed3217459d | ||
|
|
411bc5cf1c | ||
|
|
82b39190ba | ||
|
|
fd54caeb55 | ||
|
|
c4f221d778 | ||
|
|
057304e9aa | ||
|
|
63b5f921e1 | ||
|
|
eb87739060 | ||
|
|
122f819ed9 | ||
|
|
850b4f8510 | ||
|
|
6bb4e89fe2 | ||
|
|
743cb2d250 | ||
|
|
01393aff7e | ||
|
|
89b7007694 | ||
|
|
947f8e2ed4 | ||
|
|
6c329ba56f | ||
|
|
39912060f7 | ||
|
|
c142c52258 | ||
|
|
741ccfd2ce | ||
|
|
183c771d4e | ||
|
|
3d7eb55fc2 | ||
|
|
5004925444 | ||
|
|
10c3e482b4 | ||
|
|
8d8e1f3abd | ||
|
|
f8ce2af08c | ||
|
|
52b0cf43ca | ||
|
|
f6c2d40141 | ||
|
|
38c281331a | ||
|
|
26686d6b94 | ||
|
|
8648cdf8e4 | ||
|
|
7e5ce4b4f3 | ||
|
|
6c65fd910e | ||
|
|
95fdfba06d | ||
|
|
0f613d1823 | ||
|
|
6db9c79fe0 | ||
|
|
ab227a7c71 | ||
|
|
314ec48f46 | ||
|
|
67cd5e70c2 | ||
|
|
dbbaf9b969 | ||
|
|
1ac36cfcf8 | ||
|
|
95dd5aabc2 | ||
|
|
8a755676fa | ||
|
|
c3dccbb23d | ||
|
|
914861c5da | ||
|
|
9a87a7f14f | ||
|
|
533c41f143 | ||
|
|
4f3c081401 | ||
|
|
d4b20b3899 | ||
|
|
86e360dc14 | ||
|
|
c9d54de91a | ||
|
|
d30d76b7ac | ||
|
|
5fa2992bc5 | ||
|
|
020e04baf8 | ||
|
|
1bdb491376 | ||
|
|
b6b11a6d0c | ||
|
|
a78c677ca1 | ||
|
|
d0048595da | ||
|
|
46178e91ee | ||
|
|
33cc973b43 | ||
|
|
d4710cb16e | ||
|
|
600d002a35 | ||
|
|
0a1f966886 | ||
|
|
552eceabdb | ||
|
|
e3b7ba3677 | ||
|
|
6cbfde95ec | ||
|
|
6bd34fa6ea | ||
|
|
c187474499 | ||
|
|
bad5e60a8e | ||
|
|
e4dc810bff | ||
|
|
749a208773 | ||
|
|
cb7732083d | ||
|
|
e9d343d80d | ||
|
|
3871e75140 | ||
|
|
b93ac16cee | ||
|
|
bd95f33f5e | ||
|
|
e717748a3c | ||
|
|
2d2cbef8de | ||
|
|
187ee80ee3 | ||
|
|
484f134a98 | ||
|
|
1e6e9e0c0e | ||
|
|
bc9afc4554 | ||
|
|
8c3395481b | ||
|
|
b40366d1f6 | ||
|
|
a1a99b9eeb | ||
|
|
4756df08cb | ||
|
|
cf11f8ee7e | ||
|
|
6b86d91cd7 | ||
|
|
49af96b2b1 | ||
|
|
c08ebc622c | ||
|
|
7bd09ff21d | ||
|
|
6801238e3e | ||
|
|
6cac3d583f | ||
|
|
e13794cf73 | ||
|
|
5690ed7acd | ||
|
|
6b1dc9f3ce | ||
|
|
ff89148a93 | ||
|
|
93f51c1a1d | ||
|
|
e66406ffe2 | ||
|
|
03a2d603d3 | ||
|
|
a7b4e65521 | ||
|
|
241a3a6cfb |
@@ -21,3 +21,6 @@
|
||||
|
||||
# MacOS Desktop Services Store
|
||||
.DS_Store
|
||||
|
||||
# Hugo temp file
|
||||
.hugo_build.lock
|
||||
|
||||
36
.github/ISSUE_TEMPLATE/add_new_k8s_version.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/add_new_k8s_version.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Add new K8s version
|
||||
about: 'Checklist for maintainers to add new K8s minor version'
|
||||
title: 'Add new K8s version vX.X'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Note: Please update the issue title to include the new Kubernetes version number. -->
|
||||
|
||||
# Adding a new Kubernetes Version
|
||||
|
||||
## `pinniped's ci branch`
|
||||
|
||||
- [ ] Update `dockerfile-builders` pipeline
|
||||
- [ ] Update `pull-requests` pipeline
|
||||
- [ ] Update `main` pipeline
|
||||
|
||||
## `pinniped`
|
||||
|
||||
- [ ] Bump all golang dependencies (especially the `k8s.io` dependencies to use the new minor version).
|
||||
- [ ] Be sure to verify that everything compiles and unit tests pass locally. This is probably a good starting point.
|
||||
```shell
|
||||
./hack/update-go-mod/update-go-mod.sh
|
||||
./hack/module.sh unit
|
||||
./hack/prepare-for-integration-tests.sh
|
||||
```
|
||||
- [ ] Log in to github as pinniped-ci-bot, then go to [this page](https://github.com/pinniped-ci-bot?tab=packages) and change the settings for the new `k8s-code-generator-1.*` image to be publicly visible
|
||||
- [ ] Add the new K8s version to `hack/lib/kube-versions.txt` and run code generation.
|
||||
|
||||
## General Tasks
|
||||
|
||||
- [ ] Consider dropping support for any older versions of Kubernetes
|
||||
- [ ] Create stories or chores to take advantage of features in the new Kubernetes version
|
||||
- [ ] Close this issue
|
||||
33
.github/ISSUE_TEMPLATE/release_checklist.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/release_checklist.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Release checklist
|
||||
about: Checklist for maintainers to prepare for an upcoming release
|
||||
title: 'Release checklist for vX.X.X'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Note: Please update the issue title to include the planned release's version number. -->
|
||||
|
||||
# Release checklist
|
||||
|
||||
- [ ] Ensure that Pinniped's dependencies have been upgraded, to the extent desired by the team (refer to the diff output from the latest run of the [all-golang-deps-updated](https://ci.pinniped.broadcom.net/teams/main/pipelines/security-scan/jobs/all-golang-deps-updated/) CI job)
|
||||
- [ ] If you are updating golang in Pinniped, be sure to update golang in CI as well. Do a search-and-replace to update the version number everywhere in the pinniped `ci` branch.
|
||||
- [ ] If the Fosite library is being updated and the format of the content of the Supervisor's storage Secrets are changed, or if any change to our own code changes the format of the content of the Supervisor's session storage Secrets, then be sure to update the `accessTokenStorageVersion`, `authorizeCodeStorageVersion`, `oidcStorageVersion`, `pkceStorageVersion`, `refreshTokenStorageVersion`, variables in files such as `internal/fositestorage/accesstoken/accesstoken.go`. Failing tests should signal the need to update these values.
|
||||
- [ ] For go.mod direct dependencies that are v2 or above, such as `github.com/google/go-github/vXX`, check to see if there is a new major version available. Try using `hack/update-go-mod/update-majors.sh`.
|
||||
- [ ] Evaluate all `replace` directives in the `go.mod` file. Are those versions up-to-date? Can any `replace` directives be removed?
|
||||
- [ ] Evaluate all overrides in the `hack/update-go-mod/overrides.conf` file. Are those versions up-to-date? Can those overrides be removed?
|
||||
- [ ] Ensure that Pinniped's codegen is up-to-date with the latest Kubernetes releases by making sure this [file](https://github.com/vmware/pinniped/blob/main/hack/lib/kube-versions.txt) is updated compared to the latest releases listed [here for active branches](https://kubernetes.io/releases/) and [here for non-active branches](https://kubernetes.io/releases/patch-releases/#non-active-branch-history)
|
||||
- [ ] Ensure that the `k8s-code-generator` CI job definitions are up-to-date with the latest Go, K8s, and `controller-gen` versions
|
||||
- [ ] All relevant feature and docs PRs are merged
|
||||
- [ ] The [main pipeline](https://ci.pinniped.broadcom.net/teams/main/pipelines/main) is green, up to and including the `ready-to-release` job. Check that the expected git commit has passed the `ready-to-release` job.
|
||||
- [ ] Manually trigger the jobs `run-int-misc`, `run-int-cloud-providers`, and `run-int-k8s-versions` in the main pipeline to run other pre-release tests. Depending on the number of Concourse workers, you may need to run these one at a time.
|
||||
- [ ] Optional: a blog post for the release is written and submitted as a PR but not merged yet
|
||||
- [ ] All merged user stories are accepted (manually tested)
|
||||
- [ ] Only after all stories are accepted, manually trigger the `release` job to create a draft GitHub release
|
||||
- [ ] Manually edit the draft release notes on the [GitHub release](https://github.com/vmware/pinniped/releases) to describe the contents of the release, using the format which was automatically added to the draft release
|
||||
- [ ] Publish (i.e. make public) the draft release
|
||||
- [ ] After making the release public, the jobs in the [main pipeline](https://ci.pinniped.broadcom.net/teams/main/pipelines/main) beyond the release job should auto-trigger, so check to make sure that they passed
|
||||
- [ ] Edit the blog post's date to make it match the actual release date, and merge the blog post PR to make it live on the website
|
||||
- [ ] Publicize the release via tweets, etc.
|
||||
- [ ] Close this issue
|
||||
128
.github/dependabot.yml
vendored
128
.github/dependabot.yml
vendored
@@ -2,11 +2,6 @@
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
open-pull-requests-limit: 100
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
open-pull-requests-limit: 2
|
||||
@@ -14,12 +9,131 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
# Use dependabot to automate major-only dependency bumps
|
||||
- package-ecosystem: "gomod"
|
||||
open-pull-requests-limit: 2 # Not sure why there would ever be more than 1, just would not want to hide anything
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
# group all major dependency bumps together so there's only one pull request
|
||||
groups:
|
||||
go-modules:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "major"
|
||||
ignore:
|
||||
# For all packages, ignore all minor and patch updates
|
||||
- dependency-name: "*"
|
||||
update-types:
|
||||
- "version-update:semver-minor"
|
||||
- "version-update:semver-patch"
|
||||
|
||||
# Our own CI job is responsible for updating this Docker file now.
|
||||
# - package-ecosystem: "docker"
|
||||
# directory: "/"
|
||||
# schedule:
|
||||
# interval: "daily"
|
||||
|
||||
# Our own CI job is responsible for updating this Docker file now.
|
||||
# - 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"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/hack" # this should keep the FIPS dockerfile updated per https://github.com/dependabot/feedback/issues/145#issuecomment-414738498
|
||||
directory: "/dockerfiles/code-coverage-uploader/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/crane/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/deployment-yaml-formatter/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/eks-deployer/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/gh-cli/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/integration-test-runner/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/integration-test-runner-beta/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/k8s-app-deployer/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/k8s-code-generator/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/pool-trigger-resource/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/test-bitnami-ldap/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/test-cfssl/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/test-dex/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/test-forward-proxy/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/dockerfiles/test-kubectl/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/pipelines/shared-helpers/test-binaries-image/"
|
||||
open-pull-requests-limit: 100
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: ci
|
||||
|
||||
47
.github/workflows/codeql-analysis.yml
vendored
47
.github/workflows/codeql-analysis.yml
vendored
@@ -1,18 +1,23 @@
|
||||
# See https://codeql.github.com and https://github.com/github/codeql-action
|
||||
# This action runs GitHub's industry-leading semantic code analysis engine, CodeQL, against a
|
||||
# repository's source code to find security vulnerabilities. It then automatically uploads the
|
||||
# results to GitHub so they can be displayed in the repository's security tab.
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, release*, dynamic_clients ]
|
||||
branches: [ "main", release* ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main, release*, dynamic_clients ]
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '39 13 * * 2'
|
||||
- cron: '24 3 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
@@ -24,34 +29,44 @@ jobs:
|
||||
language: [ 'go', 'javascript' ]
|
||||
|
||||
steps:
|
||||
# Checkout our repository.
|
||||
# See https://github.com/actions/checkout for documentation.
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v3
|
||||
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
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# Install Go.
|
||||
# See https://github.com/actions/setup-go?tab=readme-ov-file#getting-go-version-from-the-gomod-file.
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
# 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
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ 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
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
55
.github/workflows/scorecards.yml
vendored
55
.github/workflows/scorecards.yml
vendored
@@ -1,55 +0,0 @@
|
||||
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
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -19,3 +19,6 @@
|
||||
|
||||
# MacOS Desktop Services Store
|
||||
.DS_Store
|
||||
|
||||
# Hugo temp file
|
||||
.hugo_build.lock
|
||||
|
||||
223
.golangci.yaml
223
.golangci.yaml
@@ -1,70 +1,167 @@
|
||||
# https://github.com/golangci/golangci-lint#config-file
|
||||
run:
|
||||
deadline: 1m
|
||||
skip-dirs:
|
||||
- generated
|
||||
# https://golangci-lint.run/usage/configuration/
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
# default linters
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
||||
|
||||
# additional linters for this project (we should disable these if they get annoying).
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dogsled
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- godot
|
||||
- goheader
|
||||
- goimports
|
||||
- revive
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- noctx
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- rowserrcheck
|
||||
- exportloopref
|
||||
- sqlclosecheck
|
||||
- unconvert
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
# exclude tests from some rules for things that are useful in a testing context.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- copyloopvar
|
||||
- dogsled
|
||||
- errcheck
|
||||
- exhaustive
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
|
||||
linters-settings:
|
||||
funlen:
|
||||
lines: 150
|
||||
statements: 50
|
||||
goheader:
|
||||
values:
|
||||
regexp:
|
||||
# YYYY or YYYY-YYYY
|
||||
YEARS: \d\d\d\d(-\d\d\d\d)?
|
||||
template: |-
|
||||
- gochecknoinits
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- godot
|
||||
- goheader
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- intrange
|
||||
- makezero
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- noctx
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- spancheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unused
|
||||
- whitespace
|
||||
settings:
|
||||
funlen:
|
||||
lines: 150
|
||||
statements: 50
|
||||
goheader:
|
||||
values:
|
||||
regexp:
|
||||
# YYYY or YYYY-YYYY
|
||||
YEARS: \d\d\d\d(-\d\d\d\d)?
|
||||
template: |-
|
||||
Copyright {{YEARS}} the Pinniped contributors. All Rights Reserved.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
goimports:
|
||||
local-prefixes: go.pinniped.dev
|
||||
importas:
|
||||
alias:
|
||||
- pkg: k8s.io/apimachinery/pkg/util/errors
|
||||
alias: utilerrors
|
||||
- pkg: k8s.io/apimachinery/pkg/api/errors
|
||||
alias: apierrors
|
||||
- pkg: k8s.io/apimachinery/pkg/apis/meta/v1
|
||||
alias: metav1
|
||||
- pkg: k8s.io/api/core/v1
|
||||
alias: corev1
|
||||
- pkg: github.com/coreos/go-oidc/v3/oidc
|
||||
alias: coreosoidc
|
||||
- pkg: github.com/ory/fosite/handler/oauth2
|
||||
alias: fositeoauth2
|
||||
- pkg: github.com/ory/fosite/token/jwt
|
||||
alias: fositejwt
|
||||
- pkg: github.com/go-jose/go-jose/v4/jwt
|
||||
alias: josejwt
|
||||
- pkg: github.com/go-jose/go-jose/v3
|
||||
alias: oldjosev3
|
||||
- pkg: go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1
|
||||
alias: authenticationv1alpha1
|
||||
- pkg: go.pinniped.dev/generated/latest/apis/supervisor/clientsecret/v1alpha1
|
||||
alias: clientsecretv1alpha1
|
||||
- pkg: go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1
|
||||
alias: supervisorconfigv1alpha1
|
||||
- pkg: go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1
|
||||
alias: conciergeconfigv1alpha1
|
||||
- pkg: go.pinniped.dev/generated/latest/client/concierge/clientset/versioned
|
||||
alias: conciergeclientset
|
||||
- pkg: go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/scheme
|
||||
alias: conciergeclientsetscheme
|
||||
- pkg: go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake
|
||||
alias: conciergefake
|
||||
- pkg: go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned
|
||||
alias: supervisorclientset
|
||||
- pkg: go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/scheme
|
||||
alias: supervisorclientsetscheme
|
||||
- pkg: go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake
|
||||
alias: supervisorfake
|
||||
- pkg: k8s.io/client-go/kubernetes/fake
|
||||
alias: kubefake
|
||||
- pkg: go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1
|
||||
alias: idpv1alpha1
|
||||
- pkg: go.pinniped.dev/generated/latest/client/concierge/informers/externalversions
|
||||
alias: conciergeinformers
|
||||
- pkg: go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions
|
||||
alias: supervisorinformers
|
||||
- pkg: go.pinniped.dev/internal/concierge/scheme
|
||||
alias: conciergescheme
|
||||
no-unaliased: true # All packages explicitly listed above must be aliased
|
||||
no-extra-aliases: false # Allow other aliases than the ones explicitly listed above
|
||||
revive:
|
||||
max-open-files: 2048
|
||||
rules:
|
||||
# Allow unused params that start with underscore. It can be nice to keep unused param names when implementing
|
||||
# an interface sometimes, to help readers understand why it is unused in that particular implementation.
|
||||
- name: unused-parameter
|
||||
arguments:
|
||||
- allowRegex: ^_
|
||||
spancheck:
|
||||
# https://golangci-lint.run/usage/linters/#spancheck
|
||||
checks:
|
||||
- end
|
||||
- record-error
|
||||
- set-status
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
# exclude tests from some rules for things that are useful in a testing context.
|
||||
- linters:
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
- revive
|
||||
path: _test\.go
|
||||
- linters:
|
||||
- revive
|
||||
path: internal/testutil/
|
||||
paths:
|
||||
- generated
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
settings:
|
||||
gofmt:
|
||||
# Simplify code: gofmt with `-s` option.
|
||||
# Default: true
|
||||
simplify: false
|
||||
# Apply the rewrite rules to the source before reformatting.
|
||||
# https://pkg.go.dev/cmd/gofmt
|
||||
# Default: []
|
||||
rewrite-rules:
|
||||
- pattern: interface{}
|
||||
replacement: any
|
||||
- pattern: a[b:len(a)]
|
||||
replacement: a[b:]
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- go.pinniped.dev
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- generated
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exclude: '^(site|generated)/'
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
# TODO: find a version of this to validate ytt templates?
|
||||
# - id: check-yaml
|
||||
|
||||
@@ -30,5 +30,5 @@ TMC uses Pinniped to provide a uniform authentication experience across all atta
|
||||
## Adding your organization to the list of adopters
|
||||
|
||||
If you are using Pinniped and would like to be included in the list of Pinniped Adopters, add an SVG version of your logo that is less than 150 KB to
|
||||
the [img directory](https://github.com/vmware-tanzu/pinniped/tree/main/site/themes/pinniped/static/img) in this repo and submit a pull request with your change including 1-2 sentences describing how your organization is using Pinniped. Name the image file something that
|
||||
the [img directory](https://github.com/vmware/pinniped/tree/main/site/themes/pinniped/static/img) in this repo and submit a pull request with your change including 1-2 sentences describing how your organization is using Pinniped. Name the image file something that
|
||||
reflects your company (e.g., if your company is called Acme, name the image acme.svg). Please feel free to send us a message in [#pinniped](https://kubernetes.slack.com/archives/C01BW364RJA) with any questions you may have.
|
||||
|
||||
@@ -20,26 +20,26 @@ The near-term and mid-term roadmap for the work planned for the project [maintai
|
||||
## Discussion
|
||||
|
||||
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),
|
||||
via GitHub [Discussions](https://github.com/vmware/pinniped/discussions),
|
||||
GitHub [Issues](https://github.com/vmware/pinniped/issues),
|
||||
or in the Kubernetes Slack Workspace within the [#pinniped channel](https://go.pinniped.dev/community/slack).
|
||||
Join our [Google Group](https://go.pinniped.dev/community/group) to receive updates and meeting invitations.
|
||||
|
||||
## Issues
|
||||
|
||||
Need an idea for a project to get started contributing? Take a look at the open
|
||||
[issues](https://github.com/vmware-tanzu/pinniped/issues).
|
||||
[issues](https://github.com/vmware/pinniped/issues).
|
||||
Also check to see if any open issues are labeled with
|
||||
["good first issue"](https://github.com/vmware-tanzu/pinniped/labels/good%20first%20issue)
|
||||
or ["help wanted"](https://github.com/vmware-tanzu/pinniped/labels/help%20wanted).
|
||||
["good first issue"](https://github.com/vmware/pinniped/labels/good%20first%20issue)
|
||||
or ["help wanted"](https://github.com/vmware/pinniped/labels/help%20wanted).
|
||||
|
||||
### Bugs
|
||||
|
||||
To file a bug report, please first open an
|
||||
[issue](https://github.com/vmware-tanzu/pinniped/issues/new?template=bug_report.md). The project team
|
||||
[issue](https://github.com/vmware/pinniped/issues/new?template=bug_report.md). The project team
|
||||
will work with you on your bug report.
|
||||
|
||||
Once the bug has been validated, a [pull request](https://github.com/vmware-tanzu/pinniped/compare)
|
||||
Once the bug has been validated, a [pull request](https://github.com/vmware/pinniped/compare)
|
||||
can be opened to fix the bug.
|
||||
|
||||
For specifics on what to include in your bug report, please follow the
|
||||
@@ -48,11 +48,11 @@ guidelines in the issue and pull request templates.
|
||||
### Features
|
||||
|
||||
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).
|
||||
[issue](https://github.com/vmware/pinniped/issues/new?template=feature-proposal.md)
|
||||
and tag it with `proposal`, or create a new [Discussion](https://github.com/vmware/pinniped/discussions).
|
||||
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)
|
||||
Once the feature request has been validated, a [pull request](https://github.com/vmware/pinniped/compare)
|
||||
can be opened to implement the feature.
|
||||
|
||||
For specifics on what to include in your feature request, please follow the
|
||||
@@ -114,7 +114,6 @@ go build -o pinniped ./cmd/pinniped
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
- [`chromedriver`](https://chromedriver.chromium.org/) (and [Chrome](https://www.google.com/chrome/))
|
||||
- [`docker`](https://www.docker.com/)
|
||||
- `htpasswd` (installed by default on MacOS, usually found in `apache2-utils` package for linux)
|
||||
- [`kapp`](https://carvel.dev/#getting-started)
|
||||
@@ -122,11 +121,13 @@ go build -o pinniped ./cmd/pinniped
|
||||
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
|
||||
- [`ytt`](https://carvel.dev/#getting-started)
|
||||
- [`nmap`](https://nmap.org/download.html)
|
||||
- [`openssl`](https://www.openssl.org) (installed by default on MacOS)
|
||||
- [Chrome](https://www.google.com/chrome/)
|
||||
|
||||
On macOS, these tools can be installed with [Homebrew](https://brew.sh/) (assuming you have Chrome installed already):
|
||||
|
||||
```bash
|
||||
brew install kind vmware-tanzu/carvel/ytt vmware-tanzu/carvel/kapp kubectl chromedriver nmap && brew cask install docker
|
||||
brew install kind carvel-dev/carvel/ytt carvel-dev/carvel/kapp kubectl nmap && brew cask install docker
|
||||
```
|
||||
|
||||
1. Create a kind cluster, compile, create container images, and install Pinniped and supporting test dependencies using:
|
||||
@@ -143,7 +144,7 @@ go build -o pinniped ./cmd/pinniped
|
||||
|
||||
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)).
|
||||
(see [integration/main_test.go](https://github.com/vmware/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
|
||||
@@ -151,15 +152,46 @@ go build -o pinniped ./cmd/pinniped
|
||||
|
||||
To destroy the local Kubernetes cluster, run `./hack/kind-down.sh`.
|
||||
|
||||
#### Using GoLand to Run an Integration Test
|
||||
|
||||
It can sometimes be convenient to use GoLand to run an integration test. For example, this allows using the
|
||||
GoLand debugger to debug the test itself (not the server, since that it running in-cluster).
|
||||
|
||||
Note that the output of `hack/prepare-for-integration-tests.sh` says:
|
||||
|
||||
```bash
|
||||
# Using GoLand? Paste the result of this command into GoLand's run configuration "Environment".
|
||||
# hack/integration-test-env-goland.sh | pbcopy
|
||||
```
|
||||
|
||||
After using `hack/prepare-for-integration-tests.sh`, run `hack/integration-test-env-goland.sh | pbcopy` as instructed. Then:
|
||||
|
||||
1. Select and run an integration test within GoLand. It will fail complaining about missing env vars.
|
||||
1. Pull down the menu that shows the name of the test which you just ran in the previous step, and choose "Edit Configurations...".
|
||||
1. In the "Environment" text box for the run configuration of the integration test that you just ran,
|
||||
paste the results of `hack/integration-test-env-goland.sh | pbcopy`.
|
||||
1. Apply, and then run the integration test again. This time the test will use the environment variables provided.
|
||||
|
||||
Note that if you run `hack/prepare-for-integration-tests.sh` again, then you may need to repeat these steps.
|
||||
Each run of `hack/prepare-for-integration-tests.sh` can result in different values for some of the env vars.
|
||||
|
||||
### Observing Tests on the Continuous Integration Environment
|
||||
|
||||
[CI](https://hush-house.pivotal.io/teams/tanzu-user-auth/pipelines/pinniped-pull-requests)
|
||||
will not be triggered on a pull request until the pull request is reviewed and
|
||||
CI will not be triggered on a pull request until the pull request is reviewed and
|
||||
approved for CI by a project [maintainer](MAINTAINERS.md). Once CI is triggered,
|
||||
the progress and results will appear on the Github page for that
|
||||
[pull request](https://github.com/vmware-tanzu/pinniped/pulls) as checks. Links
|
||||
[pull request](https://github.com/vmware/pinniped/pulls) as checks. Links
|
||||
will appear to view the details of each check.
|
||||
|
||||
Starting in mid-2025, Pinniped's CI system is no longer externally visible due to corporate policies.
|
||||
Please contact the maintainers for help with your PR if you encounter any CI failures.
|
||||
They will be happy to share CI logs with you directly for your PR.
|
||||
|
||||
## CI
|
||||
|
||||
Pinniped's CI configuration and code is in the [`ci`](https://github.com/vmware/pinniped/tree/ci)
|
||||
branch of this repo.
|
||||
|
||||
## Documentation
|
||||
|
||||
Any pull request which adds a new feature or changes the behavior of any feature which was previously documented
|
||||
|
||||
40
Dockerfile
40
Dockerfile
@@ -1,30 +1,48 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
# Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
FROM golang:1.20.2 as build-env
|
||||
ARG BUILD_IMAGE=golang:1.25.5@sha256:6cc2338c038bc20f96ab32848da2b5c0641bb9bb5363f2c33e9b7c8838f9a208
|
||||
ARG BASE_IMAGE=gcr.io/distroless/static:nonroot@sha256:2b7c93f6d6648c11f0e80a48558c8f77885eb0445213b8e69a6a0d7c89fc6ae4
|
||||
|
||||
# Prepare to cross-compile by always running the build stage in the build platform, not the target platform.
|
||||
FROM --platform=$BUILDPLATFORM $BUILD_IMAGE AS build-env
|
||||
|
||||
WORKDIR /work
|
||||
COPY . .
|
||||
|
||||
ARG GOPROXY
|
||||
|
||||
# Build the executable binary (CGO_ENABLED=0 means static linking)
|
||||
# Pass in GOCACHE (build cache) and GOMODCACHE (module cache) so they
|
||||
# can be re-used between image builds.
|
||||
ARG KUBE_GIT_VERSION
|
||||
ENV KUBE_GIT_VERSION=$KUBE_GIT_VERSION
|
||||
|
||||
# These will be set by buildkit automatically, e.g. TARGETOS set to "linux" and TARGETARCH set to "amd64" or "arm64".
|
||||
# Useful for building multi-arch container images.
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
# If provided, must be a comma-separated list of Go build tags.
|
||||
ARG ADDITIONAL_BUILD_TAGS
|
||||
|
||||
# Build the statically linked (CGO_ENABLED=0) binary.
|
||||
# Mount source, build cache, and module cache for performance reasons.
|
||||
# See https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/
|
||||
RUN \
|
||||
--mount=target=. \
|
||||
--mount=type=cache,target=/cache/gocache \
|
||||
--mount=type=cache,target=/cache/gomodcache \
|
||||
mkdir out && \
|
||||
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/... && \
|
||||
export GOCACHE=/cache/gocache GOMODCACHE=/cache/gomodcache CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH && \
|
||||
go build -tags $ADDITIONAL_BUILD_TAGS -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 -tags $ADDITIONAL_BUILD_TAGS -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 distroless runtime image with CA certificates, timezone data, and not much else.
|
||||
FROM gcr.io/distroless/static:nonroot@sha256:2a9e2b4fa771d31fe3346a873be845bfc2159695b9f90ca08e950497006ccc2e
|
||||
# Note that we are not using --platform here, so it will choose the base image for the target platform, not the build platform.
|
||||
# By using "distroless/static" instead of "distroless/static-debianXX" we can float on the latest stable version of debian.
|
||||
# See https://github.com/GoogleContainerTools/distroless#base-operating-system
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
# Copy the server binary from the build-env stage.
|
||||
COPY --from=build-env /usr/local/bin /usr/local/bin
|
||||
|
||||
@@ -11,7 +11,7 @@ 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).
|
||||
[code of conduct](https://github.com/vmware/pinniped/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
# Community Roles
|
||||
|
||||
@@ -29,7 +29,7 @@ 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).
|
||||
company itself violates our [Code of Conduct](https://github.com/vmware/pinniped/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
# Decision Making
|
||||
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
| Maintainer | GitHub ID | Affiliation |
|
||||
|-----------------|-----------------------------------------------------------|------------------------------------------|
|
||||
| Ben Petersen | [benjaminapetersen](https://github.com/benjaminapetersen) | [VMware](https://www.github.com/vmware/) |
|
||||
| Ryan Richard | [cfryanr](https://github.com/cfryanr) | [VMware](https://www.github.com/vmware/) |
|
||||
| Joshua T. Casey | [joshuatcasey](https://github.com/joshuatcasey) | [VMware](https://www.github.com/vmware/) |
|
||||
|
||||
## Emeritus Maintainers
|
||||
|
||||
| Maintainer | GitHub ID |
|
||||
|-------------------|---------------------------------------------------------|
|
||||
| Andrew Keesler | [ankeesler](https://github.com/ankeesler) |
|
||||
| Anjali Telang | [anjaltelang](https://github.com/anjaltelang) |
|
||||
| Margo Crawford | [margocrawf](https://github.com/margocrawf) |
|
||||
| Matt Moyer | [mattmoyer](https://github.com/mattmoyer) |
|
||||
| Mo Khan | [enj](https://github.com/enj) |
|
||||
| Pablo Schuhmacher | [pabloschuhmacher](https://github.com/pabloschuhmacher) |
|
||||
| Maintainer | GitHub ID |
|
||||
|-------------------|-----------------------------------------------------------|
|
||||
| Andrew Keesler | [ankeesler](https://github.com/ankeesler) |
|
||||
| Anjali Telang | [anjaltelang](https://github.com/anjaltelang) |
|
||||
| Ben Petersen | [benjaminapetersen](https://github.com/benjaminapetersen) |
|
||||
| Margo Crawford | [margocrawf](https://github.com/margocrawf) |
|
||||
| Matt Moyer | [mattmoyer](https://github.com/mattmoyer) |
|
||||
| Mo Khan | [enj](https://github.com/enj) |
|
||||
| Pablo Schuhmacher | [pabloschuhmacher](https://github.com/pabloschuhmacher) |
|
||||
|
||||
@@ -19,8 +19,8 @@ Care to kick the tires? It's easy to [install and try Pinniped](https://pinniped
|
||||
## Discussion
|
||||
|
||||
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),
|
||||
via GitHub [Discussions](https://github.com/vmware/pinniped/discussions),
|
||||
GitHub [Issues](https://github.com/vmware/pinniped/issues),
|
||||
or in the Kubernetes Slack Workspace within the [#pinniped channel](https://go.pinniped.dev/community/slack).
|
||||
Join our [Google Group](https://go.pinniped.dev/community/group) to receive updates and meeting invitations.
|
||||
|
||||
@@ -37,7 +37,7 @@ building and testing the code, submitting PRs, and other contributor topics.
|
||||
## 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).
|
||||
Add your own organization or product [here](https://github.com/vmware/pinniped/discussions/152).
|
||||
|
||||
## Reporting security vulnerabilities
|
||||
|
||||
@@ -47,4 +47,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-2022 the Pinniped contributors. All Rights Reserved.
|
||||
Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
|
||||
25
ROADMAP.md
25
ROADMAP.md
@@ -5,37 +5,22 @@
|
||||
This document provides a high-level overview of the next big features the maintainers are planning to work on. This
|
||||
should serve as a reference point for Pinniped users and contributors to understand where the project is heading, and
|
||||
help determine if a contribution could be conflicting with a longer term plan.
|
||||
The [Pinniped project backlog](https://github.com/orgs/vmware-tanzu/projects/43/) is prioritized based on this roadmap,
|
||||
and it provides a more granular view of what the maintainers are working on a day-to-day basis.
|
||||
|
||||
### How to help
|
||||
|
||||
Discussion on the roadmap is welcomed. If you want to provide suggestions, use cases, and feedback to an item in the
|
||||
roadmap, please reach out to the maintainers using one of the methods described in the project's
|
||||
[README.md](https://github.com/vmware-tanzu/pinniped#discussion).
|
||||
[Contributions](https://github.com/vmware-tanzu/pinniped/blob/main/CONTRIBUTING.md) to Pinniped are also welcomed.
|
||||
[README.md](https://github.com/vmware/pinniped#discussion).
|
||||
[Contributions](https://github.com/vmware/pinniped/blob/main/CONTRIBUTING.md) to Pinniped are also welcomed.
|
||||
|
||||
### How to add an item to the roadmap
|
||||
|
||||
One of the most important aspects in any open source community is the concept of proposals. Large changes to the
|
||||
codebase and / or new features should be preceded by
|
||||
a [proposal](https://github.com/vmware-tanzu/pinniped/tree/main/proposals) in our repo.
|
||||
a [proposal](https://github.com/vmware/pinniped/tree/main/proposals) in our repo.
|
||||
For smaller enhancements, you can open an issue to track that initiative or feature request.
|
||||
We work with and rely on community feedback to focus our efforts to improve Pinniped and maintain a healthy roadmap.
|
||||
|
||||
### Current Roadmap
|
||||
|
||||
The following table includes the current roadmap for Pinniped. Please take the timelines and dates as proposals and
|
||||
goals. Priorities and requirements change based on community feedback, roadblocks encountered, community contributions,
|
||||
Priorities and requirements change based on community feedback, roadblocks encountered, community contributions,
|
||||
etc. If you depend on a specific item, we encourage you to reach out for updated status information, or help us deliver
|
||||
that feature by [contributing](https://github.com/vmware-tanzu/pinniped/blob/main/CONTRIBUTING.md) to Pinniped.
|
||||
|
||||
Last Updated: Sept 2022
|
||||
|Theme|Description|Timeline|
|
||||
|--|--|--|
|
||||
|Improving Usability|Dynamic Oauth Client Support for integrating with UI/Dashboards |Sept/Oct 2022|
|
||||
|Improving Usability|Support for custom claim mappings in OIDCIdentityProvider |Q4 2022|
|
||||
|Improving Usability|Support for Multiple Identity Providers |Q4 2022|
|
||||
|Improving Security Posture|Support Audit logging of security events related to Authentication |Q4 2022|
|
||||
|Improving Security Posture|Session Management |2022/2023|
|
||||
|Improving Security Posture|Secrets Rotation and Management |2022/2023|
|
||||
that feature by [contributing](https://github.com/vmware/pinniped/blob/main/CONTRIBUTING.md) to Pinniped.
|
||||
|
||||
9
SCOPE.md
9
SCOPE.md
@@ -21,12 +21,3 @@ The following items are out of scope for the Pinniped project.
|
||||
- Standalone identity provider for general use.
|
||||
- Machine-to-machine (service) identity.
|
||||
- Running outside of Kubernetes.
|
||||
|
||||
## Roadmap
|
||||
|
||||
See our [open milestones][milestones] and the [`priority/backlog` label][backlog] for an idea about what's next on our roadmap.
|
||||
|
||||
For more details on proposing features and bugs, check out our [contributing](./CONTRIBUTING.md) doc.
|
||||
|
||||
[milestones]: https://github.com/vmware-tanzu/pinniped/milestones
|
||||
[backlog]: https://github.com/vmware-tanzu/pinniped/labels/priority%2Fbacklog
|
||||
10
SECURITY.md
10
SECURITY.md
@@ -10,11 +10,11 @@ As of right now, only the latest version of Pinniped is supported.
|
||||
|
||||
Security is of the highest importance and all security vulnerabilities or suspected security vulnerabilities should be reported to Pinniped privately, to minimize attacks against current users of Pinniped before they are fixed. Vulnerabilities will be investigated and patched on the next patch (or minor) release as soon as possible. This information could be kept entirely internal to the project.
|
||||
|
||||
If you know of a publicly disclosed security vulnerability for Pinniped, please **IMMEDIATELY** contact the VMware Security Team (security@vmware.com). The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
|
||||
If you know of a publicly disclosed security vulnerability for Pinniped, please **IMMEDIATELY** contact the VMware Security Team (vmware.psirt@broadcom.com). The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
|
||||
|
||||
**IMPORTANT: Do not file public issues on GitHub for security vulnerabilities**
|
||||
|
||||
To report a vulnerability or a security-related issue, please contact the VMware email address with the details of the vulnerability. The email will be fielded by the VMware Security Team and then shared with the Pinniped maintainers who have committer and release permissions. Emails will be addressed within 3 business days, including a detailed plan to investigate the issue and any potential workarounds to perform in the meantime. Do not report non-security-impacting bugs through this channel. Use [GitHub issues](https://github.com/vmware-tanzu/pinniped/issues/new/choose) instead.
|
||||
To report a vulnerability or a security-related issue, please contact the VMware email address with the details of the vulnerability. The email will be fielded by the VMware Security Team and then shared with the Pinniped maintainers who have committer and release permissions. Emails will be addressed within 3 business days, including a detailed plan to investigate the issue and any potential workarounds to perform in the meantime. Do not report non-security-impacting bugs through this channel. Use [GitHub issues](https://github.com/vmware/pinniped/issues/new/choose) instead.
|
||||
|
||||
## Proposed Email Content
|
||||
|
||||
@@ -48,13 +48,13 @@ The VMware Security Team will respond to vulnerability reports as follows:
|
||||
|
||||
## Public Disclosure Process
|
||||
|
||||
The Security Team publishes a [public advisory](https://github.com/vmware-tanzu/pinniped/security/advisories) to the Pinniped community via GitHub. In most cases, additional communication via Slack, Twitter, mailing lists, blog and other channels will assist in educating Pinniped users and rolling out the patched release to affected users.
|
||||
The Security Team publishes a [public advisory](https://github.com/vmware/pinniped/security/advisories) to the Pinniped community via GitHub. In most cases, additional communication via Slack, Twitter, mailing lists, blog and other channels will assist in educating Pinniped users and rolling out the patched release to affected users.
|
||||
|
||||
The Security Team will also publish any mitigating steps users can take until the fix can be applied to their Pinniped instances. Pinniped distributors will handle creating and publishing their own security advisories.
|
||||
|
||||
## Mailing lists
|
||||
|
||||
* Use security@vmware.com to report security concerns to the VMware Security Team, who uses the list to privately discuss security issues and fixes prior to disclosure. The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
|
||||
* Use vmware.psirt@broadcom.com to report security concerns to the VMware Security Team, who uses the list to privately discuss security issues and fixes prior to disclosure. The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
|
||||
* Join the [Pinniped Distributors](https://groups.google.com/g/project-pinniped-distributors) mailing list for early private information and vulnerability disclosure. Early disclosure may include mitigating steps and additional information on security patch releases. See below for information on how Pinniped distributors or vendors can apply to join this list.
|
||||
|
||||
## Early Disclosure to Pinniped Distributors List
|
||||
@@ -81,7 +81,7 @@ The information that members receive on the Pinniped Distributors mailing list m
|
||||
|
||||
Before you share any information from the list with members of your team who are required to fix the issue, these team members must agree to the same terms, and only be provided with information on a need-to-know basis.
|
||||
|
||||
In the unfortunate event that you share information beyond what is permitted by this policy, you must urgently inform the VMware Security Team (security@vmware.com) of exactly what information was leaked and to whom. If you continue to leak information and break the policy outlined here, you will be permanently removed from the list.
|
||||
In the unfortunate event that you share information beyond what is permitted by this policy, you must urgently inform the VMware Security Team (vmware.psirt@broadcom.com) of exactly what information was leaked and to whom. If you continue to leak information and break the policy outlined here, you will be permanently removed from the list.
|
||||
|
||||
## Requesting to Join
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=authentication.concierge.pinniped.dev
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authentication API.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,54 +1,280 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// Status of a JWT authenticator.
|
||||
type JWTAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// JWTAuthenticatorPhasePending is the default phase for newly-created JWTAuthenticator resources.
|
||||
JWTAuthenticatorPhasePending JWTAuthenticatorPhase = "Pending"
|
||||
|
||||
// JWTAuthenticatorPhaseReady is the phase for an JWTAuthenticator resource in a healthy state.
|
||||
JWTAuthenticatorPhaseReady JWTAuthenticatorPhase = "Ready"
|
||||
|
||||
// JWTAuthenticatorPhaseError is the phase for an JWTAuthenticator in an unhealthy state.
|
||||
JWTAuthenticatorPhaseError JWTAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// JWTAuthenticatorStatus is the status of a JWT authenticator.
|
||||
type JWTAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
|
||||
// Phase summarizes the overall status of the JWTAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase JWTAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a JWT authenticator.
|
||||
// JWTAuthenticatorSpec is the spec for configuring a JWT authenticator.
|
||||
type JWTAuthenticatorSpec struct {
|
||||
// Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is
|
||||
// issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is
|
||||
// also used to validate the "iss" JWT claim.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:Pattern=`^https://`
|
||||
Issuer string `json:"issuer"`
|
||||
|
||||
// Audience is the required value of the "aud" JWT claim.
|
||||
// audience is the required value of the "aud" JWT claim.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Audience string `json:"audience"`
|
||||
|
||||
// Claims allows customization of the claims that will be mapped to user identity
|
||||
// claims allows customization of the claims that will be mapped to user identity
|
||||
// for Kubernetes access.
|
||||
// +optional
|
||||
Claims JWTTokenClaims `json:"claims"`
|
||||
|
||||
// TLS configuration for communicating with the OIDC provider.
|
||||
// claimValidationRules are rules that are applied to validate token claims to authenticate users.
|
||||
// This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
// This is an advanced configuration option. During an end-user login flow, mistakes in this
|
||||
// configuration will cause the user's login to fail.
|
||||
// +optional
|
||||
ClaimValidationRules []ClaimValidationRule `json:"claimValidationRules,omitempty"`
|
||||
|
||||
// userValidationRules are rules that are applied to final user before completing authentication.
|
||||
// These allow invariants to be applied to incoming identities such as preventing the
|
||||
// use of the system: prefix that is commonly used by Kubernetes components.
|
||||
// The validation rules are logically ANDed together and must all return true for the validation to pass.
|
||||
// This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
// This is an advanced configuration option. During an end-user login flow, mistakes in this
|
||||
// configuration will cause the user's login to fail.
|
||||
// +optional
|
||||
UserValidationRules []UserValidationRule `json:"userValidationRules,omitempty"`
|
||||
|
||||
// tls is the configuration for communicating with the OIDC provider via TLS.
|
||||
// +optional
|
||||
TLS *TLSSpec `json:"tls,omitempty"`
|
||||
}
|
||||
|
||||
// ClaimValidationRule provides the configuration for a single claim validation rule.
|
||||
type ClaimValidationRule struct {
|
||||
// claim is the name of a required claim.
|
||||
// Only string claim keys are supported.
|
||||
// Mutually exclusive with expression and message.
|
||||
// +optional
|
||||
Claim string `json:"claim,omitempty"`
|
||||
|
||||
// requiredValue is the value of a required claim.
|
||||
// Only string claim values are supported.
|
||||
// If claim is set and requiredValue is not set, the claim must be present with a value set to the empty string.
|
||||
// Mutually exclusive with expression and message.
|
||||
// +optional
|
||||
RequiredValue string `json:"requiredValue,omitempty"`
|
||||
|
||||
// expression represents the expression which will be evaluated by CEL.
|
||||
// Must produce a boolean.
|
||||
//
|
||||
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
|
||||
// - 'claims' is a map of claim names to claim values.
|
||||
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
|
||||
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
|
||||
// Must return true for the validation to pass.
|
||||
//
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// Mutually exclusive with claim and requiredValue.
|
||||
// +optional
|
||||
Expression string `json:"expression,omitempty"`
|
||||
|
||||
// message customizes the returned error message when expression returns false.
|
||||
// message is a literal string.
|
||||
// Mutually exclusive with claim and requiredValue.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// UserValidationRule provides the configuration for a single user info validation rule.
|
||||
type UserValidationRule struct {
|
||||
// expression represents the expression which will be evaluated by CEL.
|
||||
// Must return true for the validation to pass.
|
||||
//
|
||||
// CEL expressions have access to the contents of UserInfo, organized into CEL variable:
|
||||
// - 'user' - authentication.k8s.io/v1, Kind=UserInfo object
|
||||
// Refer to https://github.com/kubernetes/api/blob/release-1.28/authentication/v1/types.go#L105-L122 for the definition.
|
||||
// API documentation: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#userinfo-v1-authentication-k8s-io
|
||||
//
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// +required
|
||||
Expression string `json:"expression"`
|
||||
|
||||
// message customizes the returned error message when rule returns false.
|
||||
// message is a literal string.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// JWTTokenClaims allows customization of the claims that will be mapped to user identity
|
||||
// for Kubernetes access.
|
||||
type JWTTokenClaims struct {
|
||||
// Groups is the name of the claim which should be read to extract the user's
|
||||
// group membership from the JWT token. When not specified, it will default to "groups".
|
||||
// username is the name of the claim which should be read to extract the
|
||||
// username from the JWT token. When not specified, it will default to "username",
|
||||
// unless usernameExpression is specified.
|
||||
//
|
||||
// Mutually exclusive with usernameExpression. Use either username or usernameExpression to
|
||||
// determine the user's username from the JWT token.
|
||||
// +optional
|
||||
Username string `json:"username"`
|
||||
|
||||
// usernameExpression represents an expression which will be evaluated by CEL.
|
||||
// The expression's result will become the user's username.
|
||||
//
|
||||
// usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
|
||||
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
|
||||
// must evaluate to the expected type without errors, or else the user's login will fail.
|
||||
// Additionally, mistakes in this configuration can cause the users to have unintended usernames.
|
||||
//
|
||||
// The expression must produce a non-empty string value.
|
||||
// If the expression uses 'claims.email', then 'claims.email_verified' must be used in
|
||||
// the expression or extra[*].valueExpression or claimValidationRules[*].expression.
|
||||
// An example claim validation rule expression that matches the validation automatically
|
||||
// applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
|
||||
// By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
|
||||
// and to make sure a non-boolean email_verified claim will be caught at runtime.
|
||||
//
|
||||
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
|
||||
// - 'claims' is a map of claim names to claim values.
|
||||
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
|
||||
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
|
||||
//
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// Mutually exclusive with username. Use either username or usernameExpression to
|
||||
// determine the user's username from the JWT token.
|
||||
// +optional
|
||||
UsernameExpression string `json:"usernameExpression,omitempty"`
|
||||
|
||||
// groups is the name of the claim which should be read to extract the user's
|
||||
// group membership from the JWT token. When not specified, it will default to "groups",
|
||||
// unless groupsExpression is specified.
|
||||
//
|
||||
// Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
|
||||
// determine the user's group membership from the JWT token.
|
||||
// +optional
|
||||
Groups string `json:"groups"`
|
||||
|
||||
// Username is the name of the claim which should be read to extract the
|
||||
// username from the JWT token. When not specified, it will default to "username".
|
||||
// groupsExpression represents an expression which will be evaluated by CEL.
|
||||
// The expression's result will become the user's group memberships.
|
||||
//
|
||||
// groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
|
||||
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
|
||||
// must evaluate to one of the expected types without errors, or else the user's login will fail.
|
||||
// Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
|
||||
//
|
||||
// The expression must produce a string or string array value.
|
||||
// "", [], and null values are treated as the group mapping not being present.
|
||||
//
|
||||
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
|
||||
// - 'claims' is a map of claim names to claim values.
|
||||
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
|
||||
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
|
||||
//
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// Mutually exclusive with groups. Use either groups or groupsExpression to
|
||||
// determine the user's group membership from the JWT token.
|
||||
// +optional
|
||||
Username string `json:"username"`
|
||||
GroupsExpression string `json:"groupsExpression,omitempty"`
|
||||
|
||||
// extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
|
||||
// as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
//
|
||||
// However, note that the Pinniped Concierge issues client certificates to users for the purpose
|
||||
// of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
|
||||
// auth extras via client certificates. When configured, these extras will appear in client
|
||||
// certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
|
||||
// Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
|
||||
// Kubernetes will ignore these extras. This is probably only useful if you are using a custom
|
||||
// authenticating proxy in front of your Kubernetes API server which can translate these OUs into
|
||||
// auth extras, as described by
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
|
||||
// This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
|
||||
// must evaluate to either a string or an array of strings, or else the user's login will fail.
|
||||
//
|
||||
// These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
|
||||
//
|
||||
// expression must produce a string or string array value.
|
||||
// If the value is empty, the extra mapping will not be present.
|
||||
//
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// hard-coded extra key/value
|
||||
// - key: "acme.io/foo"
|
||||
// valueExpression: "'bar'"
|
||||
// This will result in an extra attribute - acme.io/foo: ["bar"]
|
||||
//
|
||||
// hard-coded key, value copying claim value
|
||||
// - key: "acme.io/foo"
|
||||
// valueExpression: "claims.some_claim"
|
||||
// This will result in an extra attribute - acme.io/foo: [value of some_claim]
|
||||
//
|
||||
// hard-coded key, value derived from claim value
|
||||
// - key: "acme.io/admin"
|
||||
// valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
|
||||
// This will result in:
|
||||
// - if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
|
||||
// - if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
|
||||
//
|
||||
// +optional
|
||||
Extra []ExtraMapping `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
// ExtraMapping provides the configuration for a single extra mapping.
|
||||
type ExtraMapping struct {
|
||||
// key is a string to use as the extra attribute key.
|
||||
// key must be a domain-prefix path (e.g. example.org/foo). All characters before the first "/" must be a valid
|
||||
// subdomain as defined by RFC 1123. All characters trailing the first "/" must
|
||||
// be valid HTTP Path characters as defined by RFC 3986.
|
||||
// key must be lowercase.
|
||||
// Required to be unique.
|
||||
// Additionally, the key must not contain an equals sign ("=").
|
||||
// +required
|
||||
Key string `json:"key"`
|
||||
|
||||
// valueExpression is a CEL expression to extract extra attribute value.
|
||||
// valueExpression must produce a string or string array value.
|
||||
// "", [], and null values are treated as the extra mapping not being present.
|
||||
// Empty string values contained within a string array are filtered out.
|
||||
//
|
||||
// CEL expressions have access to the contents of the token claims, organized into CEL variable:
|
||||
// - 'claims' is a map of claim names to claim values.
|
||||
// For example, a variable named 'sub' can be accessed as 'claims.sub'.
|
||||
// Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
|
||||
//
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// +required
|
||||
ValueExpression string `json:"valueExpression"`
|
||||
}
|
||||
|
||||
// JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
@@ -62,20 +288,21 @@ type JWTTokenClaims struct {
|
||||
// +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="Status",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type JWTAuthenticator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec for configuring the authenticator.
|
||||
// spec for configuring the authenticator.
|
||||
Spec JWTAuthenticatorSpec `json:"spec"`
|
||||
|
||||
// Status of the authenticator.
|
||||
// status of the authenticator.
|
||||
Status JWTAuthenticatorStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// List of JWTAuthenticator objects.
|
||||
// JWTAuthenticatorList is a list of JWTAuthenticator objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type JWTAuthenticatorList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// ConditionStatus is effectively an enum type for Condition.Status.
|
||||
type ConditionStatus string
|
||||
|
||||
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
|
||||
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
|
||||
// can't decide if a resource is in the condition or not. In the future, we could add other
|
||||
// intermediate conditions, e.g. ConditionDegraded.
|
||||
const (
|
||||
ConditionTrue ConditionStatus = "True"
|
||||
ConditionFalse ConditionStatus = "False"
|
||||
ConditionUnknown ConditionStatus = "Unknown"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type Condition struct {
|
||||
// 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)
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation: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])$`
|
||||
// +kubebuilder:validation:MaxLength=316
|
||||
Type string `json:"type"`
|
||||
|
||||
// status of the condition, one of True, False, Unknown.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Enum=True;False;Unknown
|
||||
Status ConditionStatus `json:"status"`
|
||||
|
||||
// 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.
|
||||
// +optional
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
|
||||
// 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.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Type=string
|
||||
// +kubebuilder:validation:Format=date-time
|
||||
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
|
||||
|
||||
// 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.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:MaxLength=1024
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$`
|
||||
Reason string `json:"reason"`
|
||||
|
||||
// message is a human readable message indicating details about the transition.
|
||||
// This may be an empty string.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:MaxLength=32768
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -1,11 +1,47 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// Configuration for configuring TLS on various authenticators.
|
||||
// CertificateAuthorityDataSourceKind enumerates the sources for CA Bundles.
|
||||
//
|
||||
// +kubebuilder:validation:Enum=Secret;ConfigMap
|
||||
type CertificateAuthorityDataSourceKind string
|
||||
|
||||
const (
|
||||
// CertificateAuthorityDataSourceKindConfigMap uses a Kubernetes configmap to source CA Bundles.
|
||||
CertificateAuthorityDataSourceKindConfigMap = CertificateAuthorityDataSourceKind("ConfigMap")
|
||||
|
||||
// CertificateAuthorityDataSourceKindSecret uses a Kubernetes secret to source CA Bundles.
|
||||
// Secrets used to source CA Bundles must be of type kubernetes.io/tls or Opaque.
|
||||
CertificateAuthorityDataSourceKindSecret = CertificateAuthorityDataSourceKind("Secret")
|
||||
)
|
||||
|
||||
// CertificateAuthorityDataSourceSpec provides a source for CA bundle used for client-side TLS verification.
|
||||
type CertificateAuthorityDataSourceSpec struct {
|
||||
// Kind configures whether the CA bundle is being sourced from a Kubernetes secret or a configmap.
|
||||
// Allowed values are "Secret" or "ConfigMap".
|
||||
// "ConfigMap" uses a Kubernetes configmap to source CA Bundles.
|
||||
// "Secret" uses Kubernetes secrets of type kubernetes.io/tls or Opaque to source CA Bundles.
|
||||
Kind CertificateAuthorityDataSourceKind `json:"kind"`
|
||||
// Name is the resource name of the secret or configmap from which to read the CA bundle.
|
||||
// The referenced secret or configmap must be created in the same namespace where Pinniped Concierge is installed.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Name string `json:"name"`
|
||||
// Key is the key name within the secret or configmap from which to read the CA bundle.
|
||||
// The value found at this key in the secret or configmap must not be empty, and must be a valid PEM-encoded
|
||||
// certificate bundle.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// TLSSpec provides TLS configuration on various authenticators.
|
||||
type TLSSpec struct {
|
||||
// X.509 Certificate Authority (base64-encoded PEM bundle). If omitted, a default set of system roots will be trusted.
|
||||
// +optional
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData,omitempty"`
|
||||
// Reference to a CA bundle in a secret or a configmap.
|
||||
// Any changes to the CA bundle in the secret or configmap will be dynamically reloaded.
|
||||
// +optional
|
||||
CertificateAuthorityDataSource *CertificateAuthorityDataSourceSpec `json:"certificateAuthorityDataSource,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -12,7 +25,11 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
@@ -33,6 +50,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="Status",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type WebhookAuthenticator struct {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=config.concierge.pinniped.dev
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the Pinniped concierge configuration API.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -49,6 +49,7 @@ type CredentialIssuerSpec struct {
|
||||
}
|
||||
|
||||
// ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy.
|
||||
// Allowed values are "auto", "enabled", or "disabled".
|
||||
//
|
||||
// +kubebuilder:validation:Enum=auto;enabled;disabled
|
||||
type ImpersonationProxyMode string
|
||||
@@ -65,6 +66,7 @@ const (
|
||||
)
|
||||
|
||||
// ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy.
|
||||
// Allowed values are "LoadBalancer", "ClusterIP", or "None".
|
||||
//
|
||||
// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP;None
|
||||
type ImpersonationProxyServiceType string
|
||||
@@ -80,6 +82,28 @@ const (
|
||||
ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None")
|
||||
)
|
||||
|
||||
// ImpersonationProxyTLSSpec contains information about how the Concierge impersonation proxy should
|
||||
// serve TLS.
|
||||
//
|
||||
// If CertificateAuthorityData is not provided, the Concierge impersonation proxy will check the secret
|
||||
// for a field called "ca.crt", which will be used as the CertificateAuthorityData.
|
||||
//
|
||||
// If neither CertificateAuthorityData nor ca.crt is provided, no CA bundle will be advertised for
|
||||
// the impersonation proxy endpoint.
|
||||
type ImpersonationProxyTLSSpec struct {
|
||||
// X.509 Certificate Authority (base64-encoded PEM bundle).
|
||||
// Used to advertise the CA bundle for the impersonation proxy endpoint.
|
||||
//
|
||||
// +optional
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData,omitempty"`
|
||||
|
||||
// SecretName is the name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains
|
||||
// the TLS serving certificate for the Concierge impersonation proxy endpoint.
|
||||
//
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
}
|
||||
|
||||
// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy.
|
||||
type ImpersonationProxySpec struct {
|
||||
// Mode configures whether the impersonation proxy should be started:
|
||||
@@ -100,6 +124,13 @@ type ImpersonationProxySpec struct {
|
||||
//
|
||||
// +optional
|
||||
ExternalEndpoint string `json:"externalEndpoint,omitempty"`
|
||||
|
||||
// TLS contains information about how the Concierge impersonation proxy should serve TLS.
|
||||
//
|
||||
// If this field is empty, the impersonation proxy will generate its own TLS certificate.
|
||||
//
|
||||
// +optional
|
||||
TLS *ImpersonationProxyTLSSpec `json:"tls,omitempty"`
|
||||
}
|
||||
|
||||
// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy.
|
||||
@@ -130,24 +161,6 @@ type ImpersonationProxyServiceSpec struct {
|
||||
type CredentialIssuerStatus struct {
|
||||
// List of integration strategies that were attempted by Pinniped.
|
||||
Strategies []CredentialIssuerStrategy `json:"strategies"`
|
||||
|
||||
// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer.
|
||||
// This field is deprecated and will be removed in a future version.
|
||||
// +optional
|
||||
KubeConfigInfo *CredentialIssuerKubeConfigInfo `json:"kubeConfigInfo,omitempty"`
|
||||
}
|
||||
|
||||
// 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.
|
||||
type CredentialIssuerKubeConfigInfo struct {
|
||||
// The K8s API server URL.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:Pattern=`^https://|^http://`
|
||||
Server string `json:"server"`
|
||||
|
||||
// The K8s API server CA bundle.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData"`
|
||||
}
|
||||
|
||||
// CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/concierge/identity
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +k8s:openapi-model-package=dev.pinniped.apis.concierge.identity.v1alpha1
|
||||
// +groupName=identity.concierge.pinniped.dev
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the Pinniped identity API.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validation
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package login
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package login
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package login
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/concierge/login
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +k8s:openapi-model-package=dev.pinniped.apis.concierge.login.v1alpha1
|
||||
// +groupName=login.concierge.pinniped.dev
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the Pinniped login API.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package clientsecret
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package clientsecret
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/supervisor/clientsecret
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +k8s:openapi-model-package=dev.pinniped.apis.supervisor.clientsecret.v1alpha1
|
||||
// +groupName=clientsecret.supervisor.pinniped.dev
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the Pinniped client secret API.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/supervisor/config
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=config.supervisor.pinniped.dev
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor configuration API.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -8,14 +8,17 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
|
||||
type FederationDomainStatusCondition string
|
||||
type FederationDomainPhase string
|
||||
|
||||
const (
|
||||
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
|
||||
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
|
||||
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
|
||||
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
|
||||
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
|
||||
FederationDomainPhasePending FederationDomainPhase = "Pending"
|
||||
|
||||
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
|
||||
FederationDomainPhaseReady FederationDomainPhase = "Ready"
|
||||
|
||||
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
|
||||
FederationDomainPhaseError FederationDomainPhase = "Error"
|
||||
)
|
||||
|
||||
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
|
||||
@@ -42,6 +45,159 @@ type FederationDomainTLSSpec struct {
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
}
|
||||
|
||||
// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to
|
||||
// the transform expressions. This is a union type, and Type is the discriminator field.
|
||||
type FederationDomainTransformsConstant struct {
|
||||
// Name determines the name of the constant. It must be a valid identifier name.
|
||||
// +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$`
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=64
|
||||
Name string `json:"name"`
|
||||
|
||||
// Type determines the type of the constant, and indicates which other field should be non-empty.
|
||||
// Allowed values are "string" or "stringList".
|
||||
// +kubebuilder:validation:Enum=string;stringList
|
||||
Type string `json:"type"`
|
||||
|
||||
// StringValue should hold the value when Type is "string", and is otherwise ignored.
|
||||
// +optional
|
||||
StringValue string `json:"stringValue,omitempty"`
|
||||
|
||||
// StringListValue should hold the value when Type is "stringList", and is otherwise ignored.
|
||||
// +optional
|
||||
StringListValue []string `json:"stringListValue,omitempty"`
|
||||
}
|
||||
|
||||
// FederationDomainTransformsExpression defines a transform expression.
|
||||
type FederationDomainTransformsExpression struct {
|
||||
// Type determines the type of the expression. It must be one of the supported types.
|
||||
// Allowed values are "policy/v1", "username/v1", or "groups/v1".
|
||||
// +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1
|
||||
Type string `json:"type"`
|
||||
|
||||
// Expression is a CEL expression that will be evaluated based on the Type during an authentication.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Expression string `json:"expression"`
|
||||
|
||||
// Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects
|
||||
// an authentication attempt. When empty, a default message will be used.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// FederationDomainTransformsExample defines a transform example.
|
||||
type FederationDomainTransformsExample struct {
|
||||
// Username is the input username.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Username string `json:"username"`
|
||||
|
||||
// Groups is the input list of group names.
|
||||
// +optional
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
|
||||
// Expects is the expected output of the entire sequence of transforms when they are run against the
|
||||
// input Username and Groups.
|
||||
Expects FederationDomainTransformsExampleExpects `json:"expects"`
|
||||
}
|
||||
|
||||
// FederationDomainTransformsExampleExpects defines the expected result for a transforms example.
|
||||
type FederationDomainTransformsExampleExpects struct {
|
||||
// Username is the expected username after the transformations have been applied.
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
|
||||
// Groups is the expected list of group names after the transformations have been applied.
|
||||
// +optional
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
|
||||
// Rejected is a boolean that indicates whether authentication is expected to be rejected by a policy expression
|
||||
// after the transformations have been applied. True means that it is expected that the authentication would be
|
||||
// rejected. The default value of false means that it is expected that the authentication would not be rejected
|
||||
// by any policy expression.
|
||||
// +optional
|
||||
Rejected bool `json:"rejected,omitempty"`
|
||||
|
||||
// Message is the expected error message of the transforms. When Rejected is true, then Message is the expected
|
||||
// message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank,
|
||||
// then Message will be treated as the default error message for authentication attempts which are rejected by a
|
||||
// policy. When Rejected is false, then Message is the expected error message for some other non-policy
|
||||
// transformation error, such as a runtime error. When Rejected is false, there is no default expected Message.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain.
|
||||
type FederationDomainTransforms struct {
|
||||
// Constants defines constant variables and their values which will be made available to the transform expressions.
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +optional
|
||||
Constants []FederationDomainTransformsConstant `json:"constants,omitempty"`
|
||||
|
||||
// Expressions are an optional list of transforms and policies to be executed in the order given during every
|
||||
// authentication attempt, including during every session refresh.
|
||||
// Each is a CEL expression. It may use the basic CEL language as defined in
|
||||
// https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in
|
||||
// https://github.com/google/cel-go/tree/master/ext#strings.
|
||||
//
|
||||
// The username and groups extracted from the identity provider, and the constants defined in this CR, are
|
||||
// available as variables in all expressions. The username is provided via a variable called `username` and
|
||||
// the list of group names is provided via a variable called `groups` (which may be an empty list).
|
||||
// Each user-provided constants is provided via a variable named `strConst.varName` for string constants
|
||||
// and `strListConst.varName` for string list constants.
|
||||
//
|
||||
// The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1.
|
||||
// Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated
|
||||
// and the authentication attempt is rejected.
|
||||
// Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the
|
||||
// username or group names.
|
||||
// Each username/v1 transform must return the new username (a string), which can be the same as the old username.
|
||||
// Transformations of type username/v1 do not return group names, and therefore cannot change the group names.
|
||||
// Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old
|
||||
// groups list.
|
||||
// Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames.
|
||||
// After each expression, the new (potentially changed) username or groups get passed to the following expression.
|
||||
//
|
||||
// Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain.
|
||||
// During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the
|
||||
// authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username
|
||||
// and group names have been decided for that authentication attempt.
|
||||
//
|
||||
// +optional
|
||||
Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"`
|
||||
|
||||
// Examples can optionally be used to ensure that the sequence of transformation expressions are working as
|
||||
// expected. Examples define sample input identities which are then run through the expression list, and the
|
||||
// results are compared to the expected results. If any example in this list fails, then this
|
||||
// identity provider will not be available for use within this FederationDomain, and the error(s) will be
|
||||
// added to the FederationDomain status. This can be used to help guard against programming mistakes in the
|
||||
// expressions, and also act as living documentation for other administrators to better understand the expressions.
|
||||
// +optional
|
||||
Examples []FederationDomainTransformsExample `json:"examples,omitempty"`
|
||||
}
|
||||
|
||||
// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain.
|
||||
type FederationDomainIdentityProvider struct {
|
||||
// DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the
|
||||
// kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a
|
||||
// disruptive change for those users.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
DisplayName string `json:"displayName"`
|
||||
|
||||
// ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required.
|
||||
// If the reference cannot be resolved then the identity provider will not be made available.
|
||||
// Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider,
|
||||
// LDAPIdentityProvider, ActiveDirectoryIdentityProvider.
|
||||
ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"`
|
||||
|
||||
// Transforms is an optional way to specify transformations to be applied during user authentication and
|
||||
// session refresh.
|
||||
// +optional
|
||||
Transforms FederationDomainTransforms `json:"transforms,omitempty"`
|
||||
}
|
||||
|
||||
// FederationDomainSpec is a struct that describes an OIDC Provider.
|
||||
type FederationDomainSpec struct {
|
||||
// Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the
|
||||
@@ -53,11 +209,38 @@ type FederationDomainSpec struct {
|
||||
// See
|
||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:XValidation:message="issuer must be an HTTPS URL",rule="isURL(self) && url(self).getScheme() == 'https'"
|
||||
Issuer string `json:"issuer"`
|
||||
|
||||
// TLS configures how this FederationDomain is served over Transport Layer Security (TLS).
|
||||
// TLS specifies a secret which will contain Transport Layer Security (TLS) configuration for the FederationDomain.
|
||||
// +optional
|
||||
TLS *FederationDomainTLSSpec `json:"tls,omitempty"`
|
||||
|
||||
// IdentityProviders is the list of identity providers available for use by this FederationDomain.
|
||||
//
|
||||
// An identity provider CR (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server,
|
||||
// how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to
|
||||
// extract a normalized user identity. Normalized user identities include a username and a list of group names.
|
||||
// In contrast, IdentityProviders describes how to use that normalized identity in those Kubernetes clusters which
|
||||
// belong to this FederationDomain. Each entry in IdentityProviders can be configured with arbitrary transformations
|
||||
// on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid
|
||||
// accidental conflicts when multiple identity providers have different users with the same username (e.g.
|
||||
// "idp1:ryan" versus "idp2:ryan"). Each entry in IdentityProviders can also implement arbitrary authentication
|
||||
// rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow
|
||||
// the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could
|
||||
// disallow the authentication unless the user belongs to a specific group in the identity provider.
|
||||
//
|
||||
// For backwards compatibility with versions of Pinniped which predate support for multiple identity providers,
|
||||
// an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which
|
||||
// exist in the same namespace, but also to reject all authentication requests when there is more than one identity
|
||||
// provider currently defined. In this backwards compatibility mode, the name of the identity provider resource
|
||||
// (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this
|
||||
// FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of
|
||||
// relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead
|
||||
// explicitly list the identity provider using this IdentityProviders field.
|
||||
//
|
||||
// +optional
|
||||
IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"`
|
||||
}
|
||||
|
||||
// FederationDomainSecrets holds information about this OIDC Provider's secrets.
|
||||
@@ -86,20 +269,17 @@ type FederationDomainSecrets struct {
|
||||
|
||||
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
|
||||
type FederationDomainStatus struct {
|
||||
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
|
||||
// represent success or failure.
|
||||
// +optional
|
||||
Status FederationDomainStatusCondition `json:"status,omitempty"`
|
||||
// Phase summarizes the overall status of the FederationDomain.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase FederationDomainPhase `json:"phase,omitempty"`
|
||||
|
||||
// Message provides human-readable details about the Status.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
|
||||
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
|
||||
// around some undesirable behavior with respect to the empty metav1.Time value (see
|
||||
// https://github.com/kubernetes/kubernetes/issues/86811).
|
||||
// +optional
|
||||
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
|
||||
// Conditions represent the observations of an FederationDomain's current state.
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
|
||||
// Secrets contains information about this OIDC Provider's secrets.
|
||||
// +optional
|
||||
@@ -111,7 +291,7 @@ type FederationDomainStatus struct {
|
||||
// +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="Status",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type FederationDomain struct {
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// ConditionStatus is effectively an enum type for Condition.Status.
|
||||
type ConditionStatus string
|
||||
|
||||
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
|
||||
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
|
||||
// can't decide if a resource is in the condition or not. In the future, we could add other
|
||||
// intermediate conditions, e.g. ConditionDegraded.
|
||||
const (
|
||||
ConditionTrue ConditionStatus = "True"
|
||||
ConditionFalse ConditionStatus = "False"
|
||||
ConditionUnknown ConditionStatus = "Unknown"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type Condition struct {
|
||||
// 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)
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation: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])$`
|
||||
// +kubebuilder:validation:MaxLength=316
|
||||
Type string `json:"type"`
|
||||
|
||||
// status of the condition, one of True, False, Unknown.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Enum=True;False;Unknown
|
||||
Status ConditionStatus `json:"status"`
|
||||
|
||||
// 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.
|
||||
// +optional
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
|
||||
// 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.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Type=string
|
||||
// +kubebuilder:validation:Format=date-time
|
||||
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
|
||||
|
||||
// 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.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:MaxLength=1024
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$`
|
||||
Reason string `json:"reason"`
|
||||
|
||||
// message is a human readable message indicating details about the transition.
|
||||
// This may be an empty string.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:MaxLength=32768
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
type OIDCClientPhase string
|
||||
|
||||
const (
|
||||
// PhasePending is the default phase for newly-created OIDCClient resources.
|
||||
PhasePending OIDCClientPhase = "Pending"
|
||||
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
|
||||
OIDCClientPhasePending OIDCClientPhase = "Pending"
|
||||
|
||||
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
|
||||
PhaseReady OIDCClientPhase = "Ready"
|
||||
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
|
||||
OIDCClientPhaseReady OIDCClientPhase = "Ready"
|
||||
|
||||
// PhaseError is the phase for an OIDCClient in an unhealthy state.
|
||||
PhaseError OIDCClientPhase = "Error"
|
||||
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
|
||||
OIDCClientPhaseError OIDCClientPhase = "Error"
|
||||
)
|
||||
|
||||
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`
|
||||
@@ -71,6 +71,28 @@ type OIDCClientSpec struct {
|
||||
// +listType=set
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
AllowedScopes []Scope `json:"allowedScopes"`
|
||||
|
||||
// tokenLifetimes are the optional overrides of token lifetimes for an OIDCClient.
|
||||
// +optional
|
||||
TokenLifetimes OIDCClientTokenLifetimes `json:"tokenLifetimes,omitempty"`
|
||||
}
|
||||
|
||||
// OIDCClientTokenLifetimes describes the optional overrides of token lifetimes for an OIDCClient.
|
||||
type OIDCClientTokenLifetimes struct {
|
||||
// idTokenSeconds is the lifetime of ID tokens issued to this client, in seconds. This will choose the lifetime of
|
||||
// ID tokens returned by the authorization flow and the refresh grant. It will not influence the lifetime of the ID
|
||||
// tokens returned by RFC8693 token exchange. When null, a short-lived default value will be used.
|
||||
// This value must be between 120 and 1,800 seconds (30 minutes), inclusive. It is recommended to make these tokens
|
||||
// short-lived to force the client to perform the refresh grant often, because the refresh grant will check with the
|
||||
// external identity provider to decide if it is acceptable for the end user to continue their session, and will
|
||||
// update the end user's group memberships from the external identity provider. Giving these tokens a long life is
|
||||
// will allow the end user to continue to use a token while avoiding these updates from the external identity
|
||||
// provider. However, some web applications may have reasons specific to the design of that application to prefer
|
||||
// longer lifetimes.
|
||||
// +kubebuilder:validation:Minimum=120
|
||||
// +kubebuilder:validation:Maximum=1800
|
||||
// +optional
|
||||
IDTokenSeconds *int32 `json:"idTokenSeconds,omitempty"`
|
||||
}
|
||||
|
||||
// OIDCClientStatus is a struct that describes the actual state of an OIDCClient.
|
||||
@@ -85,7 +107,7 @@ type OIDCClientStatus struct {
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
|
||||
// totalClientSecrets is the current number of client secrets that are detected for this OIDCClient.
|
||||
// +optional
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=idp.supervisor.pinniped.dev
|
||||
// +groupGoName=IDP
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -36,6 +36,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
&LDAPIdentityProviderList{},
|
||||
&ActiveDirectoryIdentityProvider{},
|
||||
&ActiveDirectoryIdentityProviderList{},
|
||||
&GitHubIdentityProvider{},
|
||||
&GitHubIdentityProviderList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -32,7 +32,7 @@ type ActiveDirectoryIdentityProviderStatus struct {
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderBind struct {
|
||||
@@ -114,9 +114,10 @@ type ActiveDirectoryIdentityProviderGroupSearch struct {
|
||||
|
||||
// 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.
|
||||
// value of an attribute of the user entry found as a result of the user search. Which attribute's
|
||||
// value is used to replace the placeholder(s) depends on the value of UserAttributeForFilter.
|
||||
// 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:={})".
|
||||
@@ -127,6 +128,17 @@ type ActiveDirectoryIdentityProviderGroupSearch struct {
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// UserAttributeForFilter specifies which attribute's value from the user entry found as a result of
|
||||
// the user search will be used to replace the "{}" placeholder(s) in the group search Filter.
|
||||
// For example, specifying "uid" as the UserAttributeForFilter while specifying
|
||||
// "&(objectClass=posixGroup)(memberUid={})" as the Filter would search for groups by replacing
|
||||
// the "{}" placeholder in the Filter with the value of the user's "uid" attribute.
|
||||
// Optional. When not specified, the default will act as if "dn" were specified. For example, leaving
|
||||
// UserAttributeForFilter unspecified while specifying "&(objectClass=groupOfNames)(member={})" as the Filter
|
||||
// would search for groups by replacing the "{}" placeholder(s) with the dn (distinguished name) of the user.
|
||||
// +optional
|
||||
UserAttributeForFilter string `json:"userAttributeForFilter,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
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
// Copyright 2024-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type GitHubIdentityProviderPhase string
|
||||
|
||||
const (
|
||||
// GitHubPhasePending is the default phase for newly-created GitHubIdentityProvider resources.
|
||||
GitHubPhasePending GitHubIdentityProviderPhase = "Pending"
|
||||
|
||||
// GitHubPhaseReady is the phase for an GitHubIdentityProvider resource in a healthy state.
|
||||
GitHubPhaseReady GitHubIdentityProviderPhase = "Ready"
|
||||
|
||||
// GitHubPhaseError is the phase for an GitHubIdentityProvider in an unhealthy state.
|
||||
GitHubPhaseError GitHubIdentityProviderPhase = "Error"
|
||||
)
|
||||
|
||||
type GitHubAllowedAuthOrganizationsPolicy string
|
||||
|
||||
const (
|
||||
// GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers means any GitHub user is allowed to log in using this identity
|
||||
// provider, regardless of their organization membership or lack thereof.
|
||||
GitHubAllowedAuthOrganizationsPolicyAllGitHubUsers GitHubAllowedAuthOrganizationsPolicy = "AllGitHubUsers"
|
||||
|
||||
// GitHubAllowedAuthOrganizationsPolicyOnlyUsersFromAllowedOrganizations means only those users with membership in
|
||||
// the listed GitHub organizations are allowed to log in.
|
||||
GitHubAllowedAuthOrganizationsPolicyOnlyUsersFromAllowedOrganizations GitHubAllowedAuthOrganizationsPolicy = "OnlyUsersFromAllowedOrganizations"
|
||||
)
|
||||
|
||||
// GitHubIdentityProviderStatus is the status of an GitHub identity provider.
|
||||
type GitHubIdentityProviderStatus struct {
|
||||
// Phase summarizes the overall status of the GitHubIdentityProvider.
|
||||
//
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase GitHubIdentityProviderPhase `json:"phase,omitempty"`
|
||||
|
||||
// Conditions represents the observations of an identity provider's current state.
|
||||
//
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
// GitHubAPIConfig allows configuration for GitHub Enterprise Server
|
||||
type GitHubAPIConfig struct {
|
||||
// Host is required only for GitHub Enterprise Server.
|
||||
// Defaults to using GitHub's public API ("github.com").
|
||||
// For convenience, specifying "github.com" is equivalent to specifying "api.github.com".
|
||||
// Do not specify a protocol or scheme since "https://" will always be used.
|
||||
// Port is optional. Do not specify a path, query, fragment, or userinfo.
|
||||
// Only specify domain name or IP address, subdomains (optional), and port (optional).
|
||||
// IPv4 and IPv6 are supported. If using an IPv6 address with a port, you must enclose the IPv6 address
|
||||
// in square brackets. Example: "[::1]:443".
|
||||
//
|
||||
// +kubebuilder:default="github.com"
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +optional
|
||||
Host *string `json:"host"`
|
||||
|
||||
// TLS configuration for GitHub Enterprise Server.
|
||||
// Note that this field should not be needed when using GitHub's public API ("github.com").
|
||||
// However, if you choose to specify this field when using GitHub's public API, you must
|
||||
// specify a CA bundle that will verify connections to "api.github.com".
|
||||
//
|
||||
// +optional
|
||||
TLS *TLSSpec `json:"tls,omitempty"`
|
||||
}
|
||||
|
||||
// GitHubUsernameAttribute allows the user to specify which attribute(s) from GitHub to use for the username to present
|
||||
// to Kubernetes. See the response schema for
|
||||
// [Get the authenticated user](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user).
|
||||
type GitHubUsernameAttribute string
|
||||
|
||||
const (
|
||||
// GitHubUsernameID specifies using the `id` attribute from the GitHub user for the username to present to Kubernetes.
|
||||
GitHubUsernameID GitHubUsernameAttribute = "id"
|
||||
|
||||
// GitHubUsernameLogin specifies using the `login` attribute from the GitHub user as the username to present to Kubernetes.
|
||||
GitHubUsernameLogin GitHubUsernameAttribute = "login"
|
||||
|
||||
// GitHubUsernameLoginAndID specifies combining the `login` and `id` attributes from the GitHub user as the
|
||||
// username to present to Kubernetes, separated by a colon. Example: "my-login:1234"
|
||||
GitHubUsernameLoginAndID GitHubUsernameAttribute = "login:id"
|
||||
)
|
||||
|
||||
// GitHubGroupNameAttribute allows the user to specify which attribute from GitHub to use for the group
|
||||
// names to present to Kubernetes. See the response schema for
|
||||
// [List teams for the authenticated user](https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#list-teams-for-the-authenticated-user).
|
||||
type GitHubGroupNameAttribute string
|
||||
|
||||
const (
|
||||
// GitHubUseTeamNameForGroupName specifies using the GitHub team's `name` attribute as the group name to present to Kubernetes.
|
||||
GitHubUseTeamNameForGroupName GitHubGroupNameAttribute = "name"
|
||||
|
||||
// GitHubUseTeamSlugForGroupName specifies using the GitHub team's `slug` attribute as the group name to present to Kubernetes.
|
||||
GitHubUseTeamSlugForGroupName GitHubGroupNameAttribute = "slug"
|
||||
)
|
||||
|
||||
// GitHubClaims allows customization of the username and groups claims.
|
||||
type GitHubClaims struct {
|
||||
// Username configures which property of the GitHub user record shall determine the username in Kubernetes.
|
||||
//
|
||||
// Can be either "id", "login", or "login:id". Defaults to "login:id".
|
||||
//
|
||||
// GitHub's user login attributes can only contain alphanumeric characters and non-repeating hyphens,
|
||||
// and may not start or end with hyphens. GitHub users are allowed to change their login name,
|
||||
// although it is inconvenient. If a GitHub user changed their login name from "foo" to "bar",
|
||||
// then a second user might change their name from "baz" to "foo" in order to take the old
|
||||
// username of the first user. For this reason, it is not as safe to make authorization decisions
|
||||
// based only on the user's login attribute.
|
||||
//
|
||||
// If desired, an admin could configure identity transformation expressions on the Pinniped Supervisor's
|
||||
// FederationDomain to further customize how these usernames are presented to Kubernetes.
|
||||
//
|
||||
// Defaults to "login:id", which is the user login attribute, followed by a colon, followed by the unique and
|
||||
// unchanging integer ID number attribute. This blends human-readable login names with the unchanging ID value
|
||||
// from GitHub. Colons are not allowed in GitHub login attributes or ID numbers, so this is a reasonable
|
||||
// choice to concatenate the two values.
|
||||
//
|
||||
// See the response schema for
|
||||
// [Get the authenticated user](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user).
|
||||
//
|
||||
// +kubebuilder:default="login:id"
|
||||
// +kubebuilder:validation:Enum={"id","login","login:id"}
|
||||
// +optional
|
||||
Username *GitHubUsernameAttribute `json:"username"`
|
||||
|
||||
// Groups configures which property of the GitHub team record shall determine the group names in Kubernetes.
|
||||
//
|
||||
// Can be either "name" or "slug". Defaults to "slug".
|
||||
//
|
||||
// GitHub team names can contain upper and lower case characters, whitespace, and punctuation (e.g. "Kube admins!").
|
||||
//
|
||||
// GitHub team slugs are lower case alphanumeric characters and may contain dashes and underscores (e.g. "kube-admins").
|
||||
//
|
||||
// Group names as presented to Kubernetes will always be prefixed by the GitHub organization name followed by a
|
||||
// forward slash (e.g. "my-org/my-team"). GitHub organization login names can only contain alphanumeric characters
|
||||
// or single hyphens, so the first forward slash `/` will be the separator between the organization login name and
|
||||
// the team name or slug.
|
||||
//
|
||||
// If desired, an admin could configure identity transformation expressions on the Pinniped Supervisor's
|
||||
// FederationDomain to further customize how these group names are presented to Kubernetes.
|
||||
//
|
||||
// See the response schema for
|
||||
// [List teams for the authenticated user](https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#list-teams-for-the-authenticated-user).
|
||||
//
|
||||
// +kubebuilder:default=slug
|
||||
// +kubebuilder:validation:Enum=name;slug
|
||||
// +optional
|
||||
Groups *GitHubGroupNameAttribute `json:"groups"`
|
||||
}
|
||||
|
||||
// GitHubClientSpec contains information about the GitHub client that this identity provider will use
|
||||
// for web-based login flows.
|
||||
type GitHubClientSpec struct {
|
||||
// SecretName contains the name of a namespace-local Secret object that provides the clientID and
|
||||
// clientSecret for an GitHub App or GitHub OAuth2 client.
|
||||
//
|
||||
// This secret must be of type "secrets.pinniped.dev/github-client" with keys "clientID" and "clientSecret".
|
||||
//
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
SecretName string `json:"secretName"`
|
||||
}
|
||||
|
||||
type GitHubOrganizationsSpec struct {
|
||||
// Allowed values are "OnlyUsersFromAllowedOrganizations" or "AllGitHubUsers".
|
||||
// Defaults to "OnlyUsersFromAllowedOrganizations".
|
||||
//
|
||||
// Must be set to "AllGitHubUsers" if the allowed field is empty.
|
||||
//
|
||||
// This field only exists to ensure that Pinniped administrators are aware that an empty list of
|
||||
// allowedOrganizations means all GitHub users are allowed to log in.
|
||||
//
|
||||
// +kubebuilder:default=OnlyUsersFromAllowedOrganizations
|
||||
// +kubebuilder:validation:Enum=OnlyUsersFromAllowedOrganizations;AllGitHubUsers
|
||||
// +optional
|
||||
Policy *GitHubAllowedAuthOrganizationsPolicy `json:"policy"`
|
||||
|
||||
// Allowed, when specified, indicates that only users with membership in at least one of the listed
|
||||
// GitHub organizations may log in. In addition, the group membership presented to Kubernetes will only include
|
||||
// teams within the listed GitHub organizations. Additional login rules or group filtering can optionally be
|
||||
// provided as policy expression on any Pinniped Supervisor FederationDomain that includes this IDP.
|
||||
//
|
||||
// The configured GitHub App or GitHub OAuth App must be allowed to see membership in the listed organizations,
|
||||
// otherwise Pinniped will not be aware that the user belongs to the listed organization or any teams
|
||||
// within that organization.
|
||||
//
|
||||
// If no organizations are listed, you must set organizations: AllGitHubUsers.
|
||||
//
|
||||
// +kubebuilder:validation:MaxItems=64
|
||||
// +listType=set
|
||||
// +optional
|
||||
Allowed []string `json:"allowed,omitempty"`
|
||||
}
|
||||
|
||||
// GitHubAllowAuthenticationSpec allows customization of who can authenticate using this IDP and how.
|
||||
type GitHubAllowAuthenticationSpec struct {
|
||||
// Organizations allows customization of which organizations can authenticate using this IDP.
|
||||
// +kubebuilder:validation:XValidation:message="spec.allowAuthentication.organizations.policy must be 'OnlyUsersFromAllowedOrganizations' when spec.allowAuthentication.organizations.allowed has organizations listed",rule="!(has(self.allowed) && size(self.allowed) > 0 && self.policy == 'AllGitHubUsers')"
|
||||
// +kubebuilder:validation:XValidation:message="spec.allowAuthentication.organizations.policy must be 'AllGitHubUsers' when spec.allowAuthentication.organizations.allowed is empty",rule="!((!has(self.allowed) || size(self.allowed) == 0) && self.policy == 'OnlyUsersFromAllowedOrganizations')"
|
||||
Organizations GitHubOrganizationsSpec `json:"organizations"`
|
||||
}
|
||||
|
||||
// GitHubIdentityProviderSpec is the spec for configuring an GitHub identity provider.
|
||||
type GitHubIdentityProviderSpec struct {
|
||||
// GitHubAPI allows configuration for GitHub Enterprise Server
|
||||
//
|
||||
// +kubebuilder:default={}
|
||||
GitHubAPI GitHubAPIConfig `json:"githubAPI,omitempty"`
|
||||
|
||||
// Claims allows customization of the username and groups claims.
|
||||
//
|
||||
// +kubebuilder:default={}
|
||||
Claims GitHubClaims `json:"claims,omitempty"`
|
||||
|
||||
// AllowAuthentication allows customization of who can authenticate using this IDP and how.
|
||||
AllowAuthentication GitHubAllowAuthenticationSpec `json:"allowAuthentication"`
|
||||
|
||||
// Client identifies the secret with credentials for a GitHub App or GitHub OAuth2 App (a GitHub client).
|
||||
Client GitHubClientSpec `json:"client"`
|
||||
}
|
||||
|
||||
// GitHubIdentityProvider describes the configuration of an upstream GitHub identity provider.
|
||||
// This upstream provider can be configured with either a GitHub App or a GitHub OAuth2 App.
|
||||
//
|
||||
// Right now, only web-based logins are supported, for both the pinniped-cli client and clients configured
|
||||
// as OIDCClients.
|
||||
//
|
||||
// +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.githubAPI.host`
|
||||
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type GitHubIdentityProvider struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec for configuring the identity provider.
|
||||
Spec GitHubIdentityProviderSpec `json:"spec"`
|
||||
|
||||
// Status of the identity provider.
|
||||
Status GitHubIdentityProviderStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// GitHubIdentityProviderList lists GitHubIdentityProvider objects.
|
||||
//
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type GitHubIdentityProviderList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []GitHubIdentityProvider `json:"items"`
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -32,7 +32,7 @@ type LDAPIdentityProviderStatus struct {
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
type LDAPIdentityProviderBind struct {
|
||||
@@ -101,20 +101,31 @@ type LDAPIdentityProviderGroupSearch 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". 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.
|
||||
// the values of Filter, UserAttributeForFilter, Attributes, and SkipGroupRefresh are ignored.
|
||||
// +optional
|
||||
Base string `json:"base,omitempty"`
|
||||
|
||||
// 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.
|
||||
// value of an attribute of the user entry found as a result of the user search. Which attribute's
|
||||
// value is used to replace the placeholder(s) depends on the value of UserAttributeForFilter.
|
||||
// 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={}".
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// UserAttributeForFilter specifies which attribute's value from the user entry found as a result of
|
||||
// the user search will be used to replace the "{}" placeholder(s) in the group search Filter.
|
||||
// For example, specifying "uid" as the UserAttributeForFilter while specifying
|
||||
// "&(objectClass=posixGroup)(memberUid={})" as the Filter would search for groups by replacing
|
||||
// the "{}" placeholder in the Filter with the value of the user's "uid" attribute.
|
||||
// Optional. When not specified, the default will act as if "dn" were specified. For example, leaving
|
||||
// UserAttributeForFilter unspecified while specifying "&(objectClass=groupOfNames)(member={})" as the Filter
|
||||
// would search for groups by replacing the "{}" placeholder(s) with the dn (distinguished name) of the user.
|
||||
// +optional
|
||||
UserAttributeForFilter string `json:"userAttributeForFilter,omitempty"`
|
||||
|
||||
// Attributes specifies how the group's information should be read from each LDAP entry which was found as
|
||||
// the result of the group search.
|
||||
// +optional
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// ConditionStatus is effectively an enum type for Condition.Status.
|
||||
type ConditionStatus string
|
||||
|
||||
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
|
||||
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
|
||||
// can't decide if a resource is in the condition or not. In the future, we could add other
|
||||
// intermediate conditions, e.g. ConditionDegraded.
|
||||
const (
|
||||
ConditionTrue ConditionStatus = "True"
|
||||
ConditionFalse ConditionStatus = "False"
|
||||
ConditionUnknown ConditionStatus = "Unknown"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type Condition struct {
|
||||
// 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)
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation: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])$`
|
||||
// +kubebuilder:validation:MaxLength=316
|
||||
Type string `json:"type"`
|
||||
|
||||
// status of the condition, one of True, False, Unknown.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Enum=True;False;Unknown
|
||||
Status ConditionStatus `json:"status"`
|
||||
|
||||
// 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.
|
||||
// +optional
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
|
||||
// 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.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Type=string
|
||||
// +kubebuilder:validation:Format=date-time
|
||||
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
|
||||
|
||||
// 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.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:MaxLength=1024
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$`
|
||||
Reason string `json:"reason"`
|
||||
|
||||
// message is a human readable message indicating details about the transition.
|
||||
// This may be an empty string.
|
||||
// +required
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:MaxLength=32768
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -32,7 +32,7 @@ type OIDCIdentityProviderStatus struct {
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
// OIDCAuthorizationConfig provides information about how to form the OAuth2 authorization
|
||||
|
||||
@@ -1,11 +1,47 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// Configuration for TLS parameters related to identity provider integration.
|
||||
// CertificateAuthorityDataSourceKind enumerates the sources for CA Bundles.
|
||||
//
|
||||
// +kubebuilder:validation:Enum=Secret;ConfigMap
|
||||
type CertificateAuthorityDataSourceKind string
|
||||
|
||||
const (
|
||||
// CertificateAuthorityDataSourceKindConfigMap uses a Kubernetes configmap to source CA Bundles.
|
||||
CertificateAuthorityDataSourceKindConfigMap = CertificateAuthorityDataSourceKind("ConfigMap")
|
||||
|
||||
// CertificateAuthorityDataSourceKindSecret uses a Kubernetes secret to source CA Bundles.
|
||||
// Secrets used to source CA Bundles must be of type kubernetes.io/tls or Opaque.
|
||||
CertificateAuthorityDataSourceKindSecret = CertificateAuthorityDataSourceKind("Secret")
|
||||
)
|
||||
|
||||
// CertificateAuthorityDataSourceSpec provides a source for CA bundle used for client-side TLS verification.
|
||||
type CertificateAuthorityDataSourceSpec struct {
|
||||
// Kind configures whether the CA bundle is being sourced from a Kubernetes secret or a configmap.
|
||||
// Allowed values are "Secret" or "ConfigMap".
|
||||
// "ConfigMap" uses a Kubernetes configmap to source CA Bundles.
|
||||
// "Secret" uses Kubernetes secrets of type kubernetes.io/tls or Opaque to source CA Bundles.
|
||||
Kind CertificateAuthorityDataSourceKind `json:"kind"`
|
||||
// Name is the resource name of the secret or configmap from which to read the CA bundle.
|
||||
// The referenced secret or configmap must be created in the same namespace where Pinniped Supervisor is installed.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Name string `json:"name"`
|
||||
// Key is the key name within the secret or configmap from which to read the CA bundle.
|
||||
// The value found at this key in the secret or configmap must not be empty, and must be a valid PEM-encoded
|
||||
// certificate bundle.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// TLSSpec provides TLS configuration for identity provider integration.
|
||||
type TLSSpec struct {
|
||||
// X.509 Certificate Authority (base64-encoded PEM bundle). If omitted, a default set of system roots will be trusted.
|
||||
// +optional
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData,omitempty"`
|
||||
// Reference to a CA bundle in a secret or a configmap.
|
||||
// Any changes to the CA bundle in the secret or configmap will be dynamically reloaded.
|
||||
// +optional
|
||||
CertificateAuthorityDataSource *CertificateAuthorityDataSourceSpec `json:"certificateAuthorityDataSource,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
IDPTypeOIDC IDPType = "oidc"
|
||||
IDPTypeLDAP IDPType = "ldap"
|
||||
IDPTypeActiveDirectory IDPType = "activedirectory"
|
||||
IDPTypeGitHub IDPType = "github"
|
||||
|
||||
IDPFlowCLIPassword IDPFlow = "cli_password"
|
||||
IDPFlowBrowserAuthcode IDPFlow = "browser_authcode"
|
||||
@@ -54,7 +55,8 @@ type OIDCDiscoveryResponseIDPEndpoint struct {
|
||||
|
||||
// IDPDiscoveryResponse is the response of a FederationDomain's identity provider discovery endpoint.
|
||||
type IDPDiscoveryResponse struct {
|
||||
PinnipedIDPs []PinnipedIDP `json:"pinniped_identity_providers"`
|
||||
PinnipedIDPs []PinnipedIDP `json:"pinniped_identity_providers"`
|
||||
PinnipedSupportedIDPTypes []PinnipedSupportedIDPType `json:"pinniped_supported_identity_provider_types"`
|
||||
}
|
||||
|
||||
// PinnipedIDP describes a single identity provider as included in the response of a FederationDomain's
|
||||
@@ -64,3 +66,8 @@ type PinnipedIDP struct {
|
||||
Type IDPType `json:"type"`
|
||||
Flows []IDPFlow `json:"flows,omitempty"`
|
||||
}
|
||||
|
||||
// PinnipedSupportedIDPType describes a single identity provider type.
|
||||
type PinnipedSupportedIDPType struct {
|
||||
Type IDPType `json:"type"`
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package oidc
|
||||
@@ -29,6 +29,10 @@ const (
|
||||
// IDTokenClaimSubject is name of the subject claim defined by the OIDC spec.
|
||||
IDTokenClaimSubject = "sub"
|
||||
|
||||
// IDTokenSubClaimIDPNameQueryParam is the name of the query param used in the values of the "sub" claim
|
||||
// in Supervisor-issued ID tokens to identify with which external identity provider the user authenticated.
|
||||
IDTokenSubClaimIDPNameQueryParam = "idpName"
|
||||
|
||||
// IDTokenClaimAuthorizedParty is name of the authorized party claim defined by the OIDC spec.
|
||||
IDTokenClaimAuthorizedParty = "azp"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
@@ -90,12 +90,11 @@ func TestEntrypoint(t *testing.T) {
|
||||
}`,
|
||||
},
|
||||
} {
|
||||
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{}) {
|
||||
fail = func(format string, v ...any) {
|
||||
testLog.Printf(format, v...)
|
||||
panic(exited)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
@@ -38,12 +38,11 @@ func TestEntrypoint(t *testing.T) {
|
||||
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(err error, keysAndValues ...interface{}) {
|
||||
fail = func(err error, keysAndValues ...any) {
|
||||
testLog.Print(err)
|
||||
if len(keysAndValues) > 0 {
|
||||
testLog.Print(keysAndValues...)
|
||||
|
||||
46
cmd/pinniped/cmd/audit_id.go
Normal file
46
cmd/pinniped/cmd/audit_id.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.pinniped.dev/internal/httputil/roundtripper"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
type auditIDLoggerFunc func(path string, statusCode int, auditID string)
|
||||
|
||||
func logAuditID(path string, statusCode int, auditID string) {
|
||||
plog.Info("Received auditID for failed request",
|
||||
"path", path,
|
||||
"statusCode", statusCode,
|
||||
"auditID", auditID)
|
||||
}
|
||||
|
||||
func LogAuditIDTransportWrapper(rt http.RoundTripper) http.RoundTripper {
|
||||
return logAuditIDTransportWrapper(rt, logAuditID)
|
||||
}
|
||||
|
||||
func logAuditIDTransportWrapper(rt http.RoundTripper, auditIDLoggerFunc auditIDLoggerFunc) http.RoundTripper {
|
||||
return roundtripper.WrapFunc(rt, func(r *http.Request) (*http.Response, error) {
|
||||
response, responseErr := rt.RoundTrip(r)
|
||||
|
||||
if responseErr != nil ||
|
||||
response == nil ||
|
||||
response.Header.Get("audit-ID") == "" ||
|
||||
response.Request == nil ||
|
||||
response.Request.URL == nil {
|
||||
return response, responseErr
|
||||
}
|
||||
|
||||
// Use the request path from the response's request, in case the
|
||||
// original request was modified by any other roudtrippers in the chain.
|
||||
auditIDLoggerFunc(response.Request.URL.Path,
|
||||
response.StatusCode,
|
||||
response.Header.Get("audit-ID"))
|
||||
|
||||
return response, responseErr
|
||||
})
|
||||
}
|
||||
116
cmd/pinniped/cmd/audit_id_test.go
Normal file
116
cmd/pinniped/cmd/audit_id_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.pinniped.dev/internal/httputil/roundtripper"
|
||||
)
|
||||
|
||||
func TestLogAuditIDTransportWrapper(t *testing.T) {
|
||||
canonicalAuditIdHeaderName := "Audit-Id"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
response *http.Response
|
||||
responseErr error
|
||||
want func(t *testing.T, called func()) auditIDLoggerFunc
|
||||
wantCalled bool
|
||||
}{
|
||||
{
|
||||
name: "happy HTTP response - no error and no log",
|
||||
response: &http.Response{ // no headers
|
||||
StatusCode: http.StatusOK,
|
||||
Request: &http.Request{
|
||||
URL: &url.URL{
|
||||
Path: "some-path-from-response-request",
|
||||
},
|
||||
},
|
||||
},
|
||||
responseErr: nil,
|
||||
want: func(t *testing.T, called func()) auditIDLoggerFunc {
|
||||
return func(_ string, _ int, _ string) {
|
||||
called()
|
||||
}
|
||||
},
|
||||
wantCalled: false, // make it obvious
|
||||
},
|
||||
{
|
||||
name: "nil HTTP response - no error and no log",
|
||||
response: nil,
|
||||
responseErr: nil,
|
||||
want: func(t *testing.T, called func()) auditIDLoggerFunc {
|
||||
return func(_ string, _ int, _ string) {
|
||||
called()
|
||||
}
|
||||
},
|
||||
wantCalled: false, // make it obvious
|
||||
},
|
||||
{
|
||||
name: "err HTTP response - no error and no log",
|
||||
response: nil,
|
||||
responseErr: errors.New("some error"),
|
||||
want: func(t *testing.T, called func()) auditIDLoggerFunc {
|
||||
return func(_ string, _ int, _ string) {
|
||||
called()
|
||||
}
|
||||
},
|
||||
wantCalled: false, // make it obvious
|
||||
},
|
||||
{
|
||||
name: "happy HTTP response with audit-ID - logs",
|
||||
response: &http.Response{
|
||||
Header: http.Header{
|
||||
canonicalAuditIdHeaderName: []string{"some-audit-id", "some-other-audit-id-that-will-never-be-seen"},
|
||||
},
|
||||
StatusCode: http.StatusBadGateway, // statusCode does not matter
|
||||
Request: &http.Request{
|
||||
URL: &url.URL{
|
||||
Path: "some-path-from-response-request",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: func(t *testing.T, called func()) auditIDLoggerFunc {
|
||||
return func(path string, statusCode int, auditID string) {
|
||||
called()
|
||||
require.Equal(t, "some-path-from-response-request", path)
|
||||
require.Equal(t, http.StatusBadGateway, statusCode)
|
||||
require.Equal(t, "some-audit-id", auditID)
|
||||
}
|
||||
},
|
||||
wantCalled: true, // make it obvious
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require.NotNil(t, test.want)
|
||||
|
||||
mockRequest := &http.Request{
|
||||
URL: &url.URL{
|
||||
Path: "should-never-use-this-path",
|
||||
},
|
||||
}
|
||||
var mockRt roundtripper.Func = func(r *http.Request) (*http.Response, error) {
|
||||
require.Equal(t, mockRequest, r)
|
||||
return test.response, test.responseErr
|
||||
}
|
||||
called := false
|
||||
subjectRt := logAuditIDTransportWrapper(mockRt, test.want(t, func() {
|
||||
called = true
|
||||
}))
|
||||
actualResponse, err := subjectRt.RoundTrip(mockRequest) //nolint:bodyclose // there is no Body.
|
||||
require.Equal(t, test.responseErr, err) // This roundtripper only returns mocked errors.
|
||||
require.Equal(t, test.response, actualResponse)
|
||||
require.Equal(t, test.wantCalled, called,
|
||||
"want logFunc to be called: %t, actually was called: %t", test.wantCalled, called)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
conciergeconfigv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
)
|
||||
|
||||
// conciergeModeFlag represents the method by which we should connect to the Concierge on a cluster during login.
|
||||
@@ -62,12 +62,12 @@ func (f *conciergeModeFlag) Type() string {
|
||||
}
|
||||
|
||||
// MatchesFrontend returns true iff the flag matches the type of the provided frontend.
|
||||
func (f *conciergeModeFlag) MatchesFrontend(frontend *configv1alpha1.CredentialIssuerFrontend) bool {
|
||||
func (f *conciergeModeFlag) MatchesFrontend(frontend *conciergeconfigv1alpha1.CredentialIssuerFrontend) bool {
|
||||
switch *f {
|
||||
case modeImpersonationProxy:
|
||||
return frontend.Type == configv1alpha1.ImpersonationProxyFrontendType
|
||||
return frontend.Type == conciergeconfigv1alpha1.ImpersonationProxyFrontendType
|
||||
case modeTokenCredentialRequestAPI:
|
||||
return frontend.Type == configv1alpha1.TokenCredentialRequestAPIFrontendType
|
||||
return frontend.Type == conciergeconfigv1alpha1.TokenCredentialRequestAPIFrontendType
|
||||
case modeUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -13,9 +13,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
conciergeconfigv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
"go.pinniped.dev/internal/certauthority"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
)
|
||||
|
||||
func TestConciergeModeFlag(t *testing.T) {
|
||||
@@ -25,14 +24,14 @@ func TestConciergeModeFlag(t *testing.T) {
|
||||
require.NoError(t, f.Set(""))
|
||||
require.Equal(t, modeUnknown, f)
|
||||
require.EqualError(t, f.Set("foo"), `invalid mode "foo", valid modes are TokenCredentialRequestAPI and ImpersonationProxy`)
|
||||
require.True(t, f.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.TokenCredentialRequestAPIFrontendType}))
|
||||
require.True(t, f.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.ImpersonationProxyFrontendType}))
|
||||
require.True(t, f.MatchesFrontend(&conciergeconfigv1alpha1.CredentialIssuerFrontend{Type: conciergeconfigv1alpha1.TokenCredentialRequestAPIFrontendType}))
|
||||
require.True(t, f.MatchesFrontend(&conciergeconfigv1alpha1.CredentialIssuerFrontend{Type: conciergeconfigv1alpha1.ImpersonationProxyFrontendType}))
|
||||
|
||||
require.NoError(t, f.Set("TokenCredentialRequestAPI"))
|
||||
require.Equal(t, modeTokenCredentialRequestAPI, f)
|
||||
require.Equal(t, "TokenCredentialRequestAPI", f.String())
|
||||
require.True(t, f.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.TokenCredentialRequestAPIFrontendType}))
|
||||
require.False(t, f.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.ImpersonationProxyFrontendType}))
|
||||
require.True(t, f.MatchesFrontend(&conciergeconfigv1alpha1.CredentialIssuerFrontend{Type: conciergeconfigv1alpha1.TokenCredentialRequestAPIFrontendType}))
|
||||
require.False(t, f.MatchesFrontend(&conciergeconfigv1alpha1.CredentialIssuerFrontend{Type: conciergeconfigv1alpha1.ImpersonationProxyFrontendType}))
|
||||
|
||||
require.NoError(t, f.Set("tokencredentialrequestapi"))
|
||||
require.Equal(t, modeTokenCredentialRequestAPI, f)
|
||||
@@ -41,8 +40,8 @@ func TestConciergeModeFlag(t *testing.T) {
|
||||
require.NoError(t, f.Set("ImpersonationProxy"))
|
||||
require.Equal(t, modeImpersonationProxy, f)
|
||||
require.Equal(t, "ImpersonationProxy", f.String())
|
||||
require.False(t, f.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.TokenCredentialRequestAPIFrontendType}))
|
||||
require.True(t, f.MatchesFrontend(&configv1alpha1.CredentialIssuerFrontend{Type: configv1alpha1.ImpersonationProxyFrontendType}))
|
||||
require.False(t, f.MatchesFrontend(&conciergeconfigv1alpha1.CredentialIssuerFrontend{Type: conciergeconfigv1alpha1.TokenCredentialRequestAPIFrontendType}))
|
||||
require.True(t, f.MatchesFrontend(&conciergeconfigv1alpha1.CredentialIssuerFrontend{Type: conciergeconfigv1alpha1.ImpersonationProxyFrontendType}))
|
||||
|
||||
require.NoError(t, f.Set("impersonationproxy"))
|
||||
require.Equal(t, modeImpersonationProxy, f)
|
||||
@@ -52,7 +51,7 @@ func TestConciergeModeFlag(t *testing.T) {
|
||||
func TestCABundleFlag(t *testing.T) {
|
||||
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||
require.NoError(t, err)
|
||||
tmpdir := testutil.TempDir(t)
|
||||
tmpdir := t.TempDir()
|
||||
emptyFilePath := filepath.Join(tmpdir, "empty")
|
||||
require.NoError(t, os.WriteFile(emptyFilePath, []byte{}, 0600))
|
||||
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||
|
||||
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
"go.pinniped.dev/internal/kubeclient"
|
||||
)
|
||||
|
||||
// getConciergeClientsetFunc is a function that can return a clientset for the Concierge API given a
|
||||
// getClientsetsFunc is a function that can return clients for the Concierge and Kubernetes APIs given a
|
||||
// clientConfig and the apiGroupSuffix with which the API is running.
|
||||
type getConciergeClientsetFunc func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error)
|
||||
type getClientsetsFunc func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, kubernetes.Interface, aggregatorclient.Interface, error)
|
||||
|
||||
// getRealConciergeClientset returns a real implementation of a conciergeclientset.Interface.
|
||||
func getRealConciergeClientset(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
|
||||
// getRealClientsets returns real implementations of the Concierge and Kubernetes client interfaces.
|
||||
func getRealClientsets(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, kubernetes.Interface, aggregatorclient.Interface, error) {
|
||||
restConfig, err := clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
client, err := kubeclient.New(
|
||||
kubeclient.WithConfig(restConfig),
|
||||
kubeclient.WithMiddleware(groupsuffix.New(apiGroupSuffix)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return client.PinnipedConcierge, nil
|
||||
return client.PinnipedConcierge, client.Kubernetes, client.Aggregation, nil
|
||||
}
|
||||
|
||||
// newClientConfig returns a clientcmd.ClientConfig given an optional kubeconfig path override and
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -19,14 +20,15 @@ import (
|
||||
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
_ "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/utils/strings/slices"
|
||||
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||
|
||||
conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
authenticationv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
conciergeconfigv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
idpdiscoveryv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
||||
oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
|
||||
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||
@@ -36,15 +38,17 @@ import (
|
||||
)
|
||||
|
||||
type kubeconfigDeps struct {
|
||||
getenv func(key string) string
|
||||
getPathToSelf func() (string, error)
|
||||
getClientset getConciergeClientsetFunc
|
||||
getClientsets getClientsetsFunc
|
||||
log plog.MinLogger
|
||||
}
|
||||
|
||||
func kubeconfigRealDeps() kubeconfigDeps {
|
||||
return kubeconfigDeps{
|
||||
getenv: os.Getenv,
|
||||
getPathToSelf: os.Executable,
|
||||
getClientset: getRealConciergeClientset,
|
||||
getClientsets: getRealClientsets,
|
||||
log: plog.New(),
|
||||
}
|
||||
}
|
||||
@@ -96,6 +100,7 @@ type getKubeconfigParams struct {
|
||||
credentialCachePath string
|
||||
credentialCachePathSet bool
|
||||
installHint string
|
||||
pinnipedCliPath string
|
||||
}
|
||||
|
||||
type discoveryResponseScopesSupported struct {
|
||||
@@ -142,25 +147,38 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
||||
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", "", 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.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', '%s')",
|
||||
idpdiscoveryv1alpha1.IDPTypeOIDC,
|
||||
idpdiscoveryv1alpha1.IDPTypeLDAP,
|
||||
idpdiscoveryv1alpha1.IDPTypeActiveDirectory,
|
||||
idpdiscoveryv1alpha1.IDPTypeGitHub,
|
||||
),
|
||||
)
|
||||
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.kubeconfigPath, "kubeconfig", deps.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)")
|
||||
f.DurationVar(&flags.timeout, "timeout", 10*time.Minute, "Timeout for autodiscovery and validation")
|
||||
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.pinnipedCliPath, "pinniped-cli-path", "", "Full path or executable name for the Pinniped CLI binary to be embedded in the resulting kubeconfig output (e.g. 'pinniped') (default: full path of the binary used to execute this command)")
|
||||
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")
|
||||
mustMarkHidden(cmd,
|
||||
"oidc-debug-session-cache",
|
||||
"oidc-skip-listen", // --oidc-skip-listen is mainly needed for testing. We'll leave it hidden until we have a non-testing use case.
|
||||
"concierge-namespace",
|
||||
)
|
||||
|
||||
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
|
||||
mustMarkHidden(cmd, "concierge-namespace")
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
cmd.RunE = func(cmd *cobra.Command, _args []string) error {
|
||||
if flags.outputPath != "" {
|
||||
out, err := os.Create(flags.outputPath)
|
||||
if err != nil {
|
||||
@@ -179,7 +197,7 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
|
||||
ctx, cancel := context.WithTimeout(ctx, flags.timeout)
|
||||
defer cancel()
|
||||
|
||||
// the log statements in this file assume that Info logs are unconditionally printed so we set the global level to info
|
||||
// the log statements in this file assume that Info logs are unconditionally printed, so we set the global level to info
|
||||
if err := plog.ValidateAndSetLogLevelAndFormatGlobally(ctx, plog.LogSpec{Level: plog.LevelInfo, Format: plog.FormatCLI}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -199,7 +217,7 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
|
||||
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
|
||||
}
|
||||
cluster := currentKubeConfig.Clusters[currentKubeconfigNames.ClusterName]
|
||||
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
|
||||
conciergeClient, kubeClient, aggregatorClient, err := deps.getClientsets(clientConfig, flags.concierge.apiGroupSuffix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not configure Kubernetes client: %w", err)
|
||||
}
|
||||
@@ -212,13 +230,15 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
|
||||
}
|
||||
|
||||
if !flags.concierge.disabled {
|
||||
credentialIssuer, err := waitForCredentialIssuer(ctx, clientset, flags, deps)
|
||||
// Look up the Concierge's CredentialIssuer, and optionally wait for it to have no pending strategies showing in its status.
|
||||
credentialIssuer, err := waitForCredentialIssuer(ctx, conciergeClient, flags, deps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decide which Concierge authenticator should be used in the resulting kubeconfig.
|
||||
authenticator, err := lookupAuthenticator(
|
||||
clientset,
|
||||
conciergeClient,
|
||||
flags.concierge.authenticatorType,
|
||||
flags.concierge.authenticatorName,
|
||||
deps.log,
|
||||
@@ -226,10 +246,15 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Discover from the CredentialIssuer how the resulting kubeconfig should be configured to talk to this Concierge.
|
||||
if err := discoverConciergeParams(credentialIssuer, &flags, cluster, deps.log); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := discoverAuthenticatorParams(authenticator, &flags, deps.log); err != nil {
|
||||
|
||||
// Discover how the resulting kubeconfig should interact with the selected authenticator.
|
||||
// For a JWTAuthenticator, this includes discovering how to talk to the OIDC issuer configured in its spec fields.
|
||||
if err := discoverAuthenticatorParams(ctx, authenticator, &flags, kubeClient, aggregatorClient, deps.log); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -239,6 +264,7 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
|
||||
}
|
||||
|
||||
if len(flags.oidc.issuer) > 0 {
|
||||
// The OIDC provider may or may not be a Pinniped Supervisor. Find out.
|
||||
err = pinnipedSupervisorDiscovery(ctx, &flags, deps.log)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -268,7 +294,12 @@ func newExecConfig(deps kubeconfigDeps, flags getKubeconfigParams) (*clientcmdap
|
||||
|
||||
execConfig.InstallHint = flags.installHint
|
||||
var err error
|
||||
execConfig.Command, err = deps.getPathToSelf()
|
||||
execConfig.Command, err = func() (string, error) {
|
||||
if flags.pinnipedCliPath != "" {
|
||||
return flags.pinnipedCliPath, nil
|
||||
}
|
||||
return deps.getPathToSelf()
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine the Pinniped executable path: %w", err)
|
||||
}
|
||||
@@ -295,7 +326,7 @@ func newExecConfig(deps kubeconfigDeps, flags getKubeconfigParams) (*clientcmdap
|
||||
if flags.staticToken != "" && flags.staticTokenEnvName != "" {
|
||||
return nil, fmt.Errorf("only one of --static-token and --static-token-env can be specified")
|
||||
}
|
||||
execConfig.Args = append([]string{"login", "static"}, execConfig.Args...)
|
||||
execConfig.Args = slices.Concat([]string{"login", "static"}, execConfig.Args)
|
||||
if flags.staticToken != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--token="+flags.staticToken)
|
||||
}
|
||||
@@ -306,7 +337,7 @@ func newExecConfig(deps kubeconfigDeps, flags getKubeconfigParams) (*clientcmdap
|
||||
}
|
||||
|
||||
// Otherwise continue to parse the OIDC-related flags and output a config that runs `pinniped login oidc`.
|
||||
execConfig.Args = append([]string{"login", "oidc"}, execConfig.Args...)
|
||||
execConfig.Args = slices.Concat([]string{"login", "oidc"}, execConfig.Args)
|
||||
if flags.oidc.issuer == "" {
|
||||
return nil, fmt.Errorf("could not autodiscover --oidc-issuer and none was provided")
|
||||
}
|
||||
@@ -372,7 +403,7 @@ func getCurrentContext(currentKubeConfig clientcmdapi.Config, flags getKubeconfi
|
||||
return &kubeconfigNames{ContextName: contextName, UserName: ctx.AuthInfo, ClusterName: ctx.Cluster}, nil
|
||||
}
|
||||
|
||||
func waitForCredentialIssuer(ctx context.Context, clientset conciergeclientset.Interface, flags getKubeconfigParams, deps kubeconfigDeps) (*configv1alpha1.CredentialIssuer, error) {
|
||||
func waitForCredentialIssuer(ctx context.Context, clientset conciergeclientset.Interface, flags getKubeconfigParams, deps kubeconfigDeps) (*conciergeconfigv1alpha1.CredentialIssuer, error) {
|
||||
credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -385,10 +416,7 @@ func waitForCredentialIssuer(ctx context.Context, clientset conciergeclientset.I
|
||||
deadline, _ := ctx.Deadline()
|
||||
attempts := 1
|
||||
|
||||
for {
|
||||
if !hasPendingStrategy(credentialIssuer) {
|
||||
break
|
||||
}
|
||||
for hasPendingStrategy(credentialIssuer) {
|
||||
logStrategies(credentialIssuer, deps.log)
|
||||
deps.log.Info("waiting for CredentialIssuer pending strategies to finish",
|
||||
"attempts", attempts,
|
||||
@@ -408,7 +436,7 @@ func waitForCredentialIssuer(ctx context.Context, clientset conciergeclientset.I
|
||||
return credentialIssuer, nil
|
||||
}
|
||||
|
||||
func discoverConciergeParams(credentialIssuer *configv1alpha1.CredentialIssuer, flags *getKubeconfigParams, v1Cluster *clientcmdapi.Cluster, log plog.MinLogger) error {
|
||||
func discoverConciergeParams(credentialIssuer *conciergeconfigv1alpha1.CredentialIssuer, flags *getKubeconfigParams, v1Cluster *clientcmdapi.Cluster, log plog.MinLogger) error {
|
||||
// Autodiscover the --concierge-mode.
|
||||
frontend, err := getConciergeFrontend(credentialIssuer, flags.concierge.mode)
|
||||
if err != nil {
|
||||
@@ -419,10 +447,10 @@ func discoverConciergeParams(credentialIssuer *configv1alpha1.CredentialIssuer,
|
||||
// Auto-set --concierge-mode if it wasn't explicitly set.
|
||||
if flags.concierge.mode == modeUnknown {
|
||||
switch frontend.Type {
|
||||
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
|
||||
case conciergeconfigv1alpha1.TokenCredentialRequestAPIFrontendType:
|
||||
log.Info("discovered Concierge operating in TokenCredentialRequest API mode")
|
||||
flags.concierge.mode = modeTokenCredentialRequestAPI
|
||||
case configv1alpha1.ImpersonationProxyFrontendType:
|
||||
case conciergeconfigv1alpha1.ImpersonationProxyFrontendType:
|
||||
log.Info("discovered Concierge operating in impersonation proxy mode")
|
||||
flags.concierge.mode = modeImpersonationProxy
|
||||
}
|
||||
@@ -431,9 +459,9 @@ func discoverConciergeParams(credentialIssuer *configv1alpha1.CredentialIssuer,
|
||||
// Auto-set --concierge-endpoint if it wasn't explicitly set.
|
||||
if flags.concierge.endpoint == "" {
|
||||
switch frontend.Type {
|
||||
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
|
||||
case conciergeconfigv1alpha1.TokenCredentialRequestAPIFrontendType:
|
||||
flags.concierge.endpoint = v1Cluster.Server
|
||||
case configv1alpha1.ImpersonationProxyFrontendType:
|
||||
case conciergeconfigv1alpha1.ImpersonationProxyFrontendType:
|
||||
flags.concierge.endpoint = frontend.ImpersonationProxyInfo.Endpoint
|
||||
}
|
||||
log.Info("discovered Concierge endpoint", "endpoint", flags.concierge.endpoint)
|
||||
@@ -442,9 +470,9 @@ func discoverConciergeParams(credentialIssuer *configv1alpha1.CredentialIssuer,
|
||||
// Auto-set --concierge-ca-bundle if it wasn't explicitly set..
|
||||
if len(flags.concierge.caBundle) == 0 {
|
||||
switch frontend.Type {
|
||||
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
|
||||
case conciergeconfigv1alpha1.TokenCredentialRequestAPIFrontendType:
|
||||
flags.concierge.caBundle = v1Cluster.CertificateAuthorityData
|
||||
case configv1alpha1.ImpersonationProxyFrontendType:
|
||||
case conciergeconfigv1alpha1.ImpersonationProxyFrontendType:
|
||||
data, err := base64.StdEncoding.DecodeString(frontend.ImpersonationProxyInfo.CertificateAuthorityData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("autodiscovered Concierge CA bundle is invalid: %w", err)
|
||||
@@ -456,7 +484,7 @@ func discoverConciergeParams(credentialIssuer *configv1alpha1.CredentialIssuer,
|
||||
return nil
|
||||
}
|
||||
|
||||
func logStrategies(credentialIssuer *configv1alpha1.CredentialIssuer, log plog.MinLogger) {
|
||||
func logStrategies(credentialIssuer *conciergeconfigv1alpha1.CredentialIssuer, log plog.MinLogger) {
|
||||
for _, strategy := range credentialIssuer.Status.Strategies {
|
||||
log.Info("found CredentialIssuer strategy",
|
||||
"type", strategy.Type,
|
||||
@@ -467,9 +495,16 @@ func logStrategies(credentialIssuer *configv1alpha1.CredentialIssuer, log plog.M
|
||||
}
|
||||
}
|
||||
|
||||
func discoverAuthenticatorParams(authenticator metav1.Object, flags *getKubeconfigParams, log plog.MinLogger) error {
|
||||
func discoverAuthenticatorParams(
|
||||
ctx context.Context,
|
||||
authenticator metav1.Object,
|
||||
flags *getKubeconfigParams,
|
||||
kubeClient kubernetes.Interface,
|
||||
aggregatorClient aggregatorclient.Interface,
|
||||
log plog.MinLogger,
|
||||
) error {
|
||||
switch auth := authenticator.(type) {
|
||||
case *conciergev1alpha1.WebhookAuthenticator:
|
||||
case *authenticationv1alpha1.WebhookAuthenticator:
|
||||
// If the --concierge-authenticator-type/--concierge-authenticator-name flags were not set explicitly, set
|
||||
// them to point at the discovered WebhookAuthenticator.
|
||||
if flags.concierge.authenticatorType == "" && flags.concierge.authenticatorName == "" {
|
||||
@@ -477,7 +512,7 @@ func discoverAuthenticatorParams(authenticator metav1.Object, flags *getKubeconf
|
||||
flags.concierge.authenticatorType = "webhook"
|
||||
flags.concierge.authenticatorName = auth.Name
|
||||
}
|
||||
case *conciergev1alpha1.JWTAuthenticator:
|
||||
case *authenticationv1alpha1.JWTAuthenticator:
|
||||
// If the --concierge-authenticator-type/--concierge-authenticator-name flags were not set explicitly, set
|
||||
// them to point at the discovered JWTAuthenticator.
|
||||
if flags.concierge.authenticatorType == "" && flags.concierge.authenticatorName == "" {
|
||||
@@ -499,46 +534,146 @@ func discoverAuthenticatorParams(authenticator metav1.Object, flags *getKubeconf
|
||||
}
|
||||
|
||||
// If the --oidc-ca-bundle flags was not set explicitly, default it to the
|
||||
// spec.tls.certificateAuthorityData field of the JWTAuthenticator.
|
||||
if len(flags.oidc.caBundle) == 0 && auth.Spec.TLS != nil && auth.Spec.TLS.CertificateAuthorityData != "" {
|
||||
decoded, err := base64.StdEncoding.DecodeString(auth.Spec.TLS.CertificateAuthorityData)
|
||||
// spec.tls.certificateAuthorityData field of the JWTAuthenticator, if that field is set, or else
|
||||
// try to discover it from the spec.tls.certificateAuthorityDataSource, if that field is set.
|
||||
if len(flags.oidc.caBundle) == 0 && auth.Spec.TLS != nil {
|
||||
err := discoverOIDCCABundle(ctx, auth, flags, kubeClient, aggregatorClient, log)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s has invalid spec.tls.certificateAuthorityData: %w", auth.Name, err)
|
||||
return err
|
||||
}
|
||||
log.Info("discovered OIDC CA bundle", "roots", countCACerts(decoded))
|
||||
flags.oidc.caBundle = decoded
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getConciergeFrontend(credentialIssuer *configv1alpha1.CredentialIssuer, mode conciergeModeFlag) (*configv1alpha1.CredentialIssuerFrontend, error) {
|
||||
func discoverOIDCCABundle(
|
||||
ctx context.Context,
|
||||
jwtAuthenticator *authenticationv1alpha1.JWTAuthenticator,
|
||||
flags *getKubeconfigParams,
|
||||
kubeClient kubernetes.Interface,
|
||||
aggregatorClient aggregatorclient.Interface,
|
||||
log plog.MinLogger,
|
||||
) error {
|
||||
if jwtAuthenticator.Spec.TLS.CertificateAuthorityData != "" {
|
||||
decodedCABundleData, err := base64.StdEncoding.DecodeString(jwtAuthenticator.Spec.TLS.CertificateAuthorityData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s has invalid spec.tls.certificateAuthorityData: %w", jwtAuthenticator.Name, err)
|
||||
}
|
||||
log.Info("discovered OIDC CA bundle", "roots", countCACerts(decodedCABundleData))
|
||||
flags.oidc.caBundle = decodedCABundleData
|
||||
} else if jwtAuthenticator.Spec.TLS.CertificateAuthorityDataSource != nil {
|
||||
caBundleData, err := discoverOIDCCABundleFromCertificateAuthorityDataSource(
|
||||
ctx, jwtAuthenticator, flags.concierge.apiGroupSuffix, kubeClient, aggregatorClient, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flags.oidc.caBundle = caBundleData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func discoverOIDCCABundleFromCertificateAuthorityDataSource(
|
||||
ctx context.Context,
|
||||
jwtAuthenticator *authenticationv1alpha1.JWTAuthenticator,
|
||||
apiGroupSuffix string,
|
||||
kubeClient kubernetes.Interface,
|
||||
aggregatorClient aggregatorclient.Interface,
|
||||
log plog.MinLogger,
|
||||
) ([]byte, error) {
|
||||
conciergeNamespace, err := discoverConciergeNamespace(ctx, apiGroupSuffix, aggregatorClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but encountered error discovering namespace of Concierge for JWTAuthenticator %s: %w", jwtAuthenticator.Name, err)
|
||||
}
|
||||
log.Info("discovered Concierge namespace for API group suffix", "apiGroupSuffix", apiGroupSuffix)
|
||||
|
||||
var caBundleData []byte
|
||||
var keyExisted bool
|
||||
caSource := jwtAuthenticator.Spec.TLS.CertificateAuthorityDataSource
|
||||
|
||||
// Note that the Kind, Name, and Key fields must all be non-empty, and Kind must be Secret or ConfigMap, due to CRD validations.
|
||||
switch caSource.Kind {
|
||||
case authenticationv1alpha1.CertificateAuthorityDataSourceKindConfigMap:
|
||||
caBundleConfigMap, err := kubeClient.CoreV1().ConfigMaps(conciergeNamespace).Get(ctx, caSource.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but encountered error getting %s %s/%s specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource: %w",
|
||||
caSource.Kind, conciergeNamespace, caSource.Name, jwtAuthenticator.Name, err)
|
||||
}
|
||||
var caBundleDataStr string
|
||||
caBundleDataStr, keyExisted = caBundleConfigMap.Data[caSource.Key]
|
||||
caBundleData = []byte(caBundleDataStr)
|
||||
case authenticationv1alpha1.CertificateAuthorityDataSourceKindSecret:
|
||||
caBundleSecret, err := kubeClient.CoreV1().Secrets(conciergeNamespace).Get(ctx, caSource.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but encountered error getting %s %s/%s specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource: %w",
|
||||
caSource.Kind, conciergeNamespace, caSource.Name, jwtAuthenticator.Name, err)
|
||||
}
|
||||
caBundleData, keyExisted = caBundleSecret.Data[caSource.Key]
|
||||
default:
|
||||
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s spec.tls.certificateAuthorityDataSource.Kind value %q is not supported by this CLI version",
|
||||
jwtAuthenticator.Name, caSource.Kind)
|
||||
}
|
||||
|
||||
if !keyExisted {
|
||||
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but key %q specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource.key does not exist in %s %s/%s",
|
||||
caSource.Key, jwtAuthenticator.Name, caSource.Kind, conciergeNamespace, caSource.Name)
|
||||
}
|
||||
|
||||
if len(caBundleData) == 0 {
|
||||
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but key %q specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource.key exists but has empty value in %s %s/%s",
|
||||
caSource.Key, jwtAuthenticator.Name, caSource.Kind, conciergeNamespace, caSource.Name)
|
||||
}
|
||||
|
||||
numCACerts := countCACerts(caBundleData)
|
||||
if numCACerts == 0 {
|
||||
return nil, fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but value at key %q specified by JWTAuthenticator %s spec.tls.certificateAuthorityDataSource.key does not contain any CA certificates in %s %s/%s",
|
||||
caSource.Key, jwtAuthenticator.Name, caSource.Kind, conciergeNamespace, caSource.Name)
|
||||
}
|
||||
|
||||
log.Info("discovered OIDC CA bundle from JWTAuthenticator spec.tls.certificateAuthorityDataSource", "roots", numCACerts)
|
||||
return caBundleData, nil
|
||||
}
|
||||
|
||||
func discoverConciergeNamespace(ctx context.Context, apiGroupSuffix string, aggregatorClient aggregatorclient.Interface) (string, error) {
|
||||
// Let's look for the APIService for the API group of the Concierge's TokenCredentialRequest aggregated API.
|
||||
apiGroup := "login.concierge." + apiGroupSuffix
|
||||
|
||||
// List all APIServices.
|
||||
apiServiceList, err := aggregatorClient.ApiregistrationV1().APIServices().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error listing APIServices: %w", err)
|
||||
}
|
||||
|
||||
// Find one with the expected API group name.
|
||||
for _, apiService := range apiServiceList.Items {
|
||||
if apiService.Spec.Group == apiGroup {
|
||||
if apiService.Spec.Service.Namespace != "" {
|
||||
// We are assuming that all API versions (e.g. v1alpha1) of this API group are backed by service(s)
|
||||
// in the same namespace, which is the namespace of the Concierge hosting this API suffix.
|
||||
return apiService.Spec.Service.Namespace, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find any APIService for the expected API group name which contained a namespace reference in its spec.
|
||||
return "", fmt.Errorf("could not find APIService with non-empty spec.service.namespace for API group %s", apiGroup)
|
||||
}
|
||||
|
||||
func getConciergeFrontend(credentialIssuer *conciergeconfigv1alpha1.CredentialIssuer, mode conciergeModeFlag) (*conciergeconfigv1alpha1.CredentialIssuerFrontend, error) {
|
||||
for _, strategy := range credentialIssuer.Status.Strategies {
|
||||
// Skip unhealthy strategies.
|
||||
if strategy.Status != configv1alpha1.SuccessStrategyStatus {
|
||||
if strategy.Status != conciergeconfigv1alpha1.SuccessStrategyStatus {
|
||||
continue
|
||||
}
|
||||
|
||||
// Backfill the .status.strategies[].frontend field from .status.kubeConfigInfo for backwards compatibility.
|
||||
if strategy.Type == configv1alpha1.KubeClusterSigningCertificateStrategyType && strategy.Frontend == nil && credentialIssuer.Status.KubeConfigInfo != nil {
|
||||
strategy = *strategy.DeepCopy()
|
||||
strategy.Frontend = &configv1alpha1.CredentialIssuerFrontend{
|
||||
Type: configv1alpha1.TokenCredentialRequestAPIFrontendType,
|
||||
TokenCredentialRequestAPIInfo: &configv1alpha1.TokenCredentialRequestAPIInfo{
|
||||
Server: credentialIssuer.Status.KubeConfigInfo.Server,
|
||||
CertificateAuthorityData: credentialIssuer.Status.KubeConfigInfo.CertificateAuthorityData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// If the strategy frontend is still nil, skip.
|
||||
// If the strategy frontend is nil, skip.
|
||||
if strategy.Frontend == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip any unknown frontend types.
|
||||
switch strategy.Frontend.Type {
|
||||
case configv1alpha1.TokenCredentialRequestAPIFrontendType, configv1alpha1.ImpersonationProxyFrontendType:
|
||||
case conciergeconfigv1alpha1.TokenCredentialRequestAPIFrontendType,
|
||||
conciergeconfigv1alpha1.ImpersonationProxyFrontendType:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@@ -566,7 +701,7 @@ func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.E
|
||||
}
|
||||
}
|
||||
|
||||
func lookupCredentialIssuer(clientset conciergeclientset.Interface, name string, log plog.MinLogger) (*configv1alpha1.CredentialIssuer, error) {
|
||||
func lookupCredentialIssuer(clientset conciergeclientset.Interface, name string, log plog.MinLogger) (*conciergeconfigv1alpha1.CredentialIssuer, error) {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancelFunc()
|
||||
|
||||
@@ -724,13 +859,12 @@ func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconf
|
||||
func countCACerts(pemData []byte) int {
|
||||
pool := x509.NewCertPool()
|
||||
pool.AppendCertsFromPEM(pemData)
|
||||
//nolint:staticcheck // since we're not using .Subjects() to access the system pool
|
||||
return len(pool.Subjects())
|
||||
return len(pool.Subjects()) //nolint:staticcheck // there's no other clear way to mimic this legacy behavior
|
||||
}
|
||||
|
||||
func hasPendingStrategy(credentialIssuer *configv1alpha1.CredentialIssuer) bool {
|
||||
func hasPendingStrategy(credentialIssuer *conciergeconfigv1alpha1.CredentialIssuer) bool {
|
||||
for _, strategy := range credentialIssuer.Status.Strategies {
|
||||
if strategy.Reason == configv1alpha1.PendingStrategyReason {
|
||||
if strategy.Reason == conciergeconfigv1alpha1.PendingStrategyReason {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -775,12 +909,12 @@ func pinnipedSupervisorDiscovery(ctx context.Context, flags *getKubeconfigParams
|
||||
return err
|
||||
}
|
||||
if !supervisorSupportsBothUsernameAndGroupsScopes {
|
||||
flags.oidc.scopes = slices.Filter(nil, flags.oidc.scopes, func(scope string) bool {
|
||||
flags.oidc.scopes = slices.DeleteFunc(flags.oidc.scopes, func(scope string) bool {
|
||||
if scope == oidcapi.ScopeUsername || scope == oidcapi.ScopeGroups {
|
||||
log.Info("removed scope from --oidc-scopes list because it is not supported by this Supervisor", "scope", scope)
|
||||
return false // Remove username and groups scopes if there were present in the flags.
|
||||
return true // Remove username and groups scopes if there were present in the flags.
|
||||
}
|
||||
return true // Keep any other scopes in the flag list.
|
||||
return false // Keep any other scopes in the flag list.
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -39,6 +38,18 @@ const (
|
||||
// which specifies "cli_password" when using an IDE plugin where there is no interactive CLI available. This allows
|
||||
// the user to use one kubeconfig file for both flows.
|
||||
upstreamIdentityProviderFlowEnvVarName = "PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW"
|
||||
|
||||
// When using a browser-based login flow, the user may skip printing the login URL to the screen in the case
|
||||
// where the browser was launched with the login URL. This can be useful, for example, when using a console-based
|
||||
// UI like k9s, to avoid having any output to stderr which may confuse the UI. Set this env var to "true" to
|
||||
// skip printing the URL.
|
||||
skipPrintLoginURLEnvVarName = "PINNIPED_SKIP_PRINT_LOGIN_URL"
|
||||
|
||||
// Set this env var to "true" to cause debug logs to be printed to stderr.
|
||||
debugEnvVarName = "PINNIPED_DEBUG"
|
||||
|
||||
// The value to use for true/false env vars to enable the behavior caused by the env var.
|
||||
envVarTruthyValue = "true"
|
||||
)
|
||||
|
||||
//nolint:gochecknoinits
|
||||
@@ -47,9 +58,10 @@ func init() {
|
||||
}
|
||||
|
||||
type oidcLoginCommandDeps struct {
|
||||
lookupEnv func(string) (string, bool)
|
||||
login func(string, string, ...oidcclient.Option) (*oidctypes.Token, error)
|
||||
exchangeToken func(context.Context, *conciergeclient.Client, string) (*clientauthv1beta1.ExecCredential, error)
|
||||
lookupEnv func(string) (string, bool)
|
||||
login func(string, string, ...oidcclient.Option) (*oidctypes.Token, error)
|
||||
exchangeToken func(context.Context, *conciergeclient.Client, string) (*clientauthv1beta1.ExecCredential, error)
|
||||
optionsFactory OIDCClientOptions
|
||||
}
|
||||
|
||||
func oidcLoginCommandRealDeps() oidcLoginCommandDeps {
|
||||
@@ -59,6 +71,7 @@ func oidcLoginCommandRealDeps() oidcLoginCommandDeps {
|
||||
exchangeToken: func(ctx context.Context, client *conciergeclient.Client, token string) (*clientauthv1beta1.ExecCredential, error) {
|
||||
return client.ExchangeToken(ctx, token)
|
||||
},
|
||||
optionsFactory: &clientOptions{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,14 +141,23 @@ 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", 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.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', '%s')",
|
||||
idpdiscoveryv1alpha1.IDPTypeOIDC,
|
||||
idpdiscoveryv1alpha1.IDPTypeLDAP,
|
||||
idpdiscoveryv1alpha1.IDPTypeActiveDirectory,
|
||||
idpdiscoveryv1alpha1.IDPTypeGitHub,
|
||||
))
|
||||
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) }
|
||||
cmd.RunE = func(cmd *cobra.Command, _args []string) error { return runOIDCLogin(cmd, deps, flags) }
|
||||
|
||||
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
|
||||
mustMarkHidden(cmd, "concierge-namespace")
|
||||
@@ -154,43 +176,45 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
|
||||
// If the hidden --debug-session-cache option is passed, log all the errors from the session cache.
|
||||
if flags.debugSessionCache {
|
||||
logger := plog.WithName("session")
|
||||
sessionOptions = append(sessionOptions, filesession.WithErrorReporter(func(err error) {
|
||||
logger.Error("error during session cache operation", err)
|
||||
pLogger.Error("error during session cache operation", err)
|
||||
}))
|
||||
}
|
||||
sessionCache := filesession.New(flags.sessionCachePath, sessionOptions...)
|
||||
|
||||
// Initialize the login handler.
|
||||
opts := []oidcclient.Option{
|
||||
oidcclient.WithContext(cmd.Context()),
|
||||
oidcclient.WithLogger(plog.Logr()), //nolint:staticcheck // old code with lots of log statements
|
||||
oidcclient.WithScopes(flags.scopes),
|
||||
oidcclient.WithSessionCache(sessionCache),
|
||||
deps.optionsFactory.WithContext(cmd.Context()),
|
||||
deps.optionsFactory.WithLoginLogger(pLogger),
|
||||
deps.optionsFactory.WithScopes(flags.scopes),
|
||||
deps.optionsFactory.WithSessionCache(sessionCache),
|
||||
}
|
||||
|
||||
skipPrintLoginURL, _ := deps.lookupEnv(skipPrintLoginURLEnvVarName)
|
||||
if skipPrintLoginURL == envVarTruthyValue {
|
||||
opts = append(opts, deps.optionsFactory.WithSkipPrintLoginURL())
|
||||
}
|
||||
|
||||
if flags.listenPort != 0 {
|
||||
opts = append(opts, oidcclient.WithListenPort(flags.listenPort))
|
||||
opts = append(opts, deps.optionsFactory.WithListenPort(flags.listenPort))
|
||||
}
|
||||
|
||||
if flags.requestAudience != "" {
|
||||
opts = append(opts, oidcclient.WithRequestAudience(flags.requestAudience))
|
||||
opts = append(opts, deps.optionsFactory.WithRequestAudience(flags.requestAudience))
|
||||
}
|
||||
|
||||
if flags.upstreamIdentityProviderName != "" {
|
||||
opts = append(opts, oidcclient.WithUpstreamIdentityProvider(
|
||||
opts = append(opts, deps.optionsFactory.WithUpstreamIdentityProvider(
|
||||
flags.upstreamIdentityProviderName, flags.upstreamIdentityProviderType))
|
||||
}
|
||||
|
||||
flowOpts, err := flowOptions(
|
||||
idpdiscoveryv1alpha1.IDPType(flags.upstreamIdentityProviderType),
|
||||
idpdiscoveryv1alpha1.IDPFlow(flags.upstreamIdentityProviderFlow),
|
||||
deps,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
requestedFlow, flowSource := idpdiscoveryv1alpha1.IDPFlow(flags.upstreamIdentityProviderFlow), "--upstream-identity-provider-flow"
|
||||
if flowOverride, hasFlowOverride := deps.lookupEnv(upstreamIdentityProviderFlowEnvVarName); hasFlowOverride {
|
||||
requestedFlow, flowSource = idpdiscoveryv1alpha1.IDPFlow(flowOverride), upstreamIdentityProviderFlowEnvVarName
|
||||
}
|
||||
if requestedFlow != "" {
|
||||
opts = append(opts, deps.optionsFactory.WithLoginFlow(requestedFlow, flowSource))
|
||||
}
|
||||
opts = append(opts, flowOpts...)
|
||||
|
||||
var concierge *conciergeclient.Client
|
||||
if flags.conciergeEnabled {
|
||||
@@ -200,6 +224,7 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
|
||||
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
|
||||
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
|
||||
conciergeclient.WithTransportWrapper(LogAuditIDTransportWrapper),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid Concierge parameters: %w", err)
|
||||
@@ -208,12 +233,12 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
|
||||
// --skip-browser skips opening the browser.
|
||||
if flags.skipBrowser {
|
||||
opts = append(opts, oidcclient.WithSkipBrowserOpen())
|
||||
opts = append(opts, deps.optionsFactory.WithSkipBrowserOpen())
|
||||
}
|
||||
|
||||
// --skip-listen skips starting the localhost callback listener.
|
||||
if flags.skipListen {
|
||||
opts = append(opts, oidcclient.WithSkipListen())
|
||||
opts = append(opts, deps.optionsFactory.WithSkipListen())
|
||||
}
|
||||
|
||||
if len(flags.caBundlePaths) > 0 || len(flags.caBundleData) > 0 {
|
||||
@@ -221,7 +246,7 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, oidcclient.WithClient(client))
|
||||
opts = append(opts, deps.optionsFactory.WithClient(client))
|
||||
}
|
||||
// Look up cached credentials based on a hash of all the CLI arguments and the cluster info.
|
||||
cacheKey := struct {
|
||||
@@ -241,12 +266,12 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
}
|
||||
|
||||
pLogger.Debug("Performing OIDC login", "issuer", flags.issuer, "client id", flags.clientID)
|
||||
// Do the basic login to get an OIDC token.
|
||||
// Do the basic login to get an OIDC token. Although this can return several tokens, we only need the ID token here.
|
||||
token, err := deps.login(flags.issuer, flags.clientID, opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not complete Pinniped login: %w", err)
|
||||
}
|
||||
cred := tokenCredential(token)
|
||||
cred := tokenCredential(token.IDToken)
|
||||
|
||||
// If the concierge was configured, exchange the credential for a separate short-lived, cluster-specific credential.
|
||||
if concierge != nil {
|
||||
@@ -271,60 +296,6 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
return json.NewEncoder(cmd.OutOrStdout()).Encode(cred)
|
||||
}
|
||||
|
||||
func flowOptions(
|
||||
requestedIDPType idpdiscoveryv1alpha1.IDPType,
|
||||
requestedFlow idpdiscoveryv1alpha1.IDPFlow,
|
||||
deps oidcLoginCommandDeps,
|
||||
) ([]oidcclient.Option, error) {
|
||||
useCLIFlow := []oidcclient.Option{oidcclient.WithCLISendingCredentials()}
|
||||
|
||||
// If the env var is set to override the --upstream-identity-provider-type flag, then override it.
|
||||
flowOverride, hasFlowOverride := deps.lookupEnv(upstreamIdentityProviderFlowEnvVarName)
|
||||
flowSource := "--upstream-identity-provider-flow"
|
||||
if hasFlowOverride {
|
||||
requestedFlow = idpdiscoveryv1alpha1.IDPFlow(flowOverride)
|
||||
flowSource = upstreamIdentityProviderFlowEnvVarName
|
||||
}
|
||||
|
||||
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(
|
||||
"%s value not recognized for identity provider type %q: %s (supported values: %s)",
|
||||
flowSource, 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:
|
||||
return nil, nil // browser authcode flow is the default Option, so don't need to return an Option here
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"%s value not recognized for identity provider type %q: %s (supported values: %s)",
|
||||
flowSource, requestedIDPType, requestedFlow,
|
||||
strings.Join([]string{idpdiscoveryv1alpha1.IDPFlowCLIPassword.String(), idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode.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 {
|
||||
@@ -344,32 +315,31 @@ func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, er
|
||||
return phttp.Default(pool), nil
|
||||
}
|
||||
|
||||
func tokenCredential(token *oidctypes.Token) *clientauthv1beta1.ExecCredential {
|
||||
func tokenCredential(idToken *oidctypes.IDToken) *clientauthv1beta1.ExecCredential {
|
||||
cred := clientauthv1beta1.ExecCredential{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ExecCredential",
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
},
|
||||
Status: &clientauthv1beta1.ExecCredentialStatus{
|
||||
Token: token.IDToken.Token,
|
||||
Token: idToken.Token,
|
||||
},
|
||||
}
|
||||
if !token.IDToken.Expiry.IsZero() {
|
||||
cred.Status.ExpirationTimestamp = &token.IDToken.Expiry
|
||||
if !idToken.Expiry.IsZero() {
|
||||
cred.Status.ExpirationTimestamp = &idToken.Expiry
|
||||
}
|
||||
return &cred
|
||||
}
|
||||
|
||||
func SetLogLevel(ctx context.Context, lookupEnv func(string) (string, bool)) (plog.Logger, error) {
|
||||
debug, _ := lookupEnv("PINNIPED_DEBUG")
|
||||
if debug == "true" {
|
||||
debug, _ := lookupEnv(debugEnvVarName)
|
||||
if debug == envVarTruthyValue {
|
||||
err := plog.ValidateAndSetLogLevelAndFormatGlobally(ctx, plog.LogSpec{Level: plog.LevelDebug, Format: plog.FormatCLI})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
logger := plog.New().WithName("pinniped-login")
|
||||
return logger, nil
|
||||
return plog.New(), nil
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -10,18 +10,20 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/mock/gomock"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
clocktesting "k8s.io/utils/clock/testing"
|
||||
|
||||
idpdiscoveryv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
||||
oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
|
||||
"go.pinniped.dev/internal/certauthority"
|
||||
"go.pinniped.dev/internal/here"
|
||||
"go.pinniped.dev/internal/mocks/mockoidcclientoptions"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/pkg/conciergeclient"
|
||||
@@ -34,7 +36,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
|
||||
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||
require.NoError(t, err)
|
||||
tmpdir := testutil.TempDir(t)
|
||||
tmpdir := t.TempDir()
|
||||
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
||||
require.NoError(t, os.WriteFile(testCABundlePath, testCA.Bundle(), 0600))
|
||||
|
||||
@@ -44,6 +46,13 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
nowStr := now.Local().Format(time.RFC1123)
|
||||
|
||||
defaultWantedOptions := func(f *mockoidcclientoptions.MockOIDCClientOptions) {
|
||||
f.EXPECT().WithContext(gomock.Any())
|
||||
f.EXPECT().WithLoginLogger(gomock.Any())
|
||||
f.EXPECT().WithScopes([]string{oidcapi.ScopeOfflineAccess, oidcapi.ScopeOpenID, oidcapi.ScopeRequestAudience, oidcapi.ScopeUsername, oidcapi.ScopeGroups})
|
||||
f.EXPECT().WithSessionCache(gomock.Any())
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
@@ -53,6 +62,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
wantError bool
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
wantOptions func(f *mockoidcclientoptions.MockOIDCClientOptions)
|
||||
wantOptionsCount int
|
||||
wantLogs []string
|
||||
}{
|
||||
@@ -93,7 +103,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
--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', 'activedirectory') (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', 'github') (default "oidc")
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -111,7 +121,8 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--issuer", "test-issuer",
|
||||
"--enable-concierge",
|
||||
},
|
||||
wantError: true,
|
||||
wantOptions: defaultWantedOptions,
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: invalid Concierge parameters: endpoint must not be empty
|
||||
`),
|
||||
@@ -123,7 +134,8 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--issuer", "test-issuer",
|
||||
"--ca-bundle", "./does/not/exist",
|
||||
},
|
||||
wantError: true,
|
||||
wantOptions: defaultWantedOptions,
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not read --ca-bundle: open ./does/not/exist: no such file or directory
|
||||
`),
|
||||
@@ -135,7 +147,8 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--issuer", "test-issuer",
|
||||
"--ca-bundle-data", "invalid-base64",
|
||||
},
|
||||
wantError: true,
|
||||
wantOptions: defaultWantedOptions,
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not read --ca-bundle-data: illegal base64 data at input byte 7
|
||||
`),
|
||||
@@ -150,34 +163,12 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--concierge-authenticator-name", "test-authenticator",
|
||||
"--concierge-endpoint", "https://127.0.0.1:1234/",
|
||||
},
|
||||
wantError: true,
|
||||
wantOptions: defaultWantedOptions,
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: invalid Concierge parameters: invalid API group suffix: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "invalid upstream type is an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--upstream-identity-provider-type", "invalid",
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: --upstream-identity-provider-type value not recognized: invalid (supported values: oidc, ldap, activedirectory)
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "invalid upstream type when flow override env var is used is still an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--upstream-identity-provider-type", "invalid",
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "browser_authcode"},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: --upstream-identity-provider-type value not recognized: invalid (supported values: oidc, ldap, activedirectory)
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "oidc upstream type with default flow is allowed",
|
||||
args: []string{
|
||||
@@ -186,268 +177,57 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--upstream-identity-provider-type", "oidc",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptions: defaultWantedOptions,
|
||||
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 CLI flow is allowed",
|
||||
name: "PINNIPED_SKIP_PRINT_LOGIN_URL adds an option",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "oidc",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
env: map[string]string{"PINNIPED_SKIP_PRINT_LOGIN_URL": "true"},
|
||||
wantOptions: func(f *mockoidcclientoptions.MockOIDCClientOptions) {
|
||||
defaultWantedOptions(f)
|
||||
f.EXPECT().WithSkipPrintLoginURL()
|
||||
},
|
||||
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: "--upstream-identity-provider-flow adds an option",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--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 CLI flow in flow override env var 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
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "cli_password"},
|
||||
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 with browser flow in flow override env var 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
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "browser_authcode"},
|
||||
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: "oidc upstream type with unsupported flow in flow override env var is an error",
|
||||
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
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "foo"},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW value not recognized for identity provider type "oidc": foo (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",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
wantOptions: func(f *mockoidcclientoptions.MockOIDCClientOptions) {
|
||||
defaultWantedOptions(f)
|
||||
f.EXPECT().WithLoginFlow(idpdiscoveryv1alpha1.IDPFlowCLIPassword, "--upstream-identity-provider-flow")
|
||||
},
|
||||
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: "activedirectory upstream type with default flow is allowed",
|
||||
name: "PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW adds an option that overrides --upstream-identity-provider-flow",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--upstream-identity-provider-flow", "ignored-value-from-param",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "actual-value-from-env"},
|
||||
wantOptions: func(f *mockoidcclientoptions.MockOIDCClientOptions) {
|
||||
defaultWantedOptions(f)
|
||||
f.EXPECT().WithLoginFlow(idpdiscoveryv1alpha1.IDPFlow("actual-value-from-env"), "PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW")
|
||||
},
|
||||
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 browser_authcode flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--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: "ldap upstream type with CLI flow in flow override env var is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--upstream-identity-provider-flow", "browser_authcode",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "cli_password"},
|
||||
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 browser_authcode flow in flow override env var 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
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "browser_authcode"},
|
||||
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: "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", "foo",
|
||||
"--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": foo (supported values: cli_password, browser_authcode)
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "ldap upstream type with unsupported flow in flow override env var is an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--upstream-identity-provider-flow", "browser_authcode",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "foo"},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW value not recognized for identity provider type "ldap": foo (supported values: cli_password, browser_authcode)
|
||||
`),
|
||||
},
|
||||
{
|
||||
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 browser_authcode is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--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: "active directory upstream type with CLI flow in flow override env var is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--upstream-identity-provider-flow", "browser_authcode",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "cli_password"},
|
||||
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 browser_authcode in flow override env var 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
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "browser_authcode"},
|
||||
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: "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", "foo",
|
||||
"--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": foo (supported values: cli_password, browser_authcode)
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "active directory upstream type with unsupported flow in flow override env var is an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--upstream-identity-provider-flow", "browser_authcode",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
env: map[string]string{"PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW": "foo"},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW value not recognized for identity provider type "activedirectory": foo (supported values: cli_password, browser_authcode)
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "login error",
|
||||
args: []string{
|
||||
@@ -456,6 +236,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
loginErr: fmt.Errorf("some login error"),
|
||||
wantOptions: defaultWantedOptions,
|
||||
wantOptionsCount: 4,
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
@@ -474,6 +255,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
conciergeErr: fmt.Errorf("some concierge error"),
|
||||
wantOptions: defaultWantedOptions,
|
||||
wantOptionsCount: 4,
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
@@ -488,11 +270,12 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantOptions: defaultWantedOptions,
|
||||
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",
|
||||
wantLogs: []string{
|
||||
nowStr + ` pinniped-login cmd/login_oidc.go:243 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
|
||||
nowStr + ` pinniped-login cmd/login_oidc.go:263 No concierge configured, skipping token credential exchange`,
|
||||
nowStr + ` cmd/login_oidc.go:268 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
|
||||
nowStr + ` cmd/login_oidc.go:288 No concierge configured, skipping token credential exchange`,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -513,31 +296,49 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--concierge-endpoint", "https://127.0.0.1:1234/",
|
||||
"--concierge-ca-bundle-data", base64.StdEncoding.EncodeToString(testCA.Bundle()),
|
||||
"--concierge-api-group-suffix", "some.suffix.com",
|
||||
"--credential-cache", testutil.TempDir(t) + "/credentials.yaml", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
"--credential-cache", t.TempDir() + "/credentials.yaml", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
"--upstream-identity-provider-name", "some-upstream-name",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--upstream-identity-provider-flow", "some-flow-type",
|
||||
},
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantOptionsCount: 11,
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true", "PINNIPED_SKIP_PRINT_LOGIN_URL": "true"},
|
||||
wantOptions: func(f *mockoidcclientoptions.MockOIDCClientOptions) {
|
||||
f.EXPECT().WithContext(gomock.Any())
|
||||
f.EXPECT().WithLoginLogger(gomock.Any())
|
||||
f.EXPECT().WithScopes([]string{oidcapi.ScopeOfflineAccess, oidcapi.ScopeOpenID, oidcapi.ScopeRequestAudience, oidcapi.ScopeUsername, oidcapi.ScopeGroups})
|
||||
f.EXPECT().WithSessionCache(gomock.Any())
|
||||
f.EXPECT().WithListenPort(uint16(1234))
|
||||
f.EXPECT().WithSkipBrowserOpen()
|
||||
f.EXPECT().WithSkipListen()
|
||||
f.EXPECT().WithSkipPrintLoginURL()
|
||||
f.EXPECT().WithClient(gomock.Any())
|
||||
f.EXPECT().WithRequestAudience("cluster-1234")
|
||||
f.EXPECT().WithLoginFlow(idpdiscoveryv1alpha1.IDPFlow("some-flow-type"), "--upstream-identity-provider-flow")
|
||||
f.EXPECT().WithUpstreamIdentityProvider("some-upstream-name", "ldap")
|
||||
},
|
||||
wantOptionsCount: 12,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"exchanged-token"}}` + "\n",
|
||||
wantLogs: []string{
|
||||
nowStr + ` pinniped-login cmd/login_oidc.go:243 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
|
||||
nowStr + ` pinniped-login cmd/login_oidc.go:253 Exchanging token for cluster credential {"endpoint": "https://127.0.0.1:1234/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
|
||||
nowStr + ` pinniped-login cmd/login_oidc.go:261 Successfully exchanged token for cluster credential.`,
|
||||
nowStr + ` pinniped-login cmd/login_oidc.go:268 caching cluster credential for future use.`,
|
||||
nowStr + ` cmd/login_oidc.go:268 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
|
||||
nowStr + ` cmd/login_oidc.go:278 Exchanging token for cluster credential {"endpoint": "https://127.0.0.1:1234/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
|
||||
nowStr + ` cmd/login_oidc.go:286 Successfully exchanged token for cluster credential.`,
|
||||
nowStr + ` cmd/login_oidc.go:293 caching cluster credential for future use.`,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
fakeClock := clocktesting.NewFakeClock(now)
|
||||
ctx := plog.TestZapOverrides(context.Background(), t, &buf, nil, zap.WithClock(plog.ZapClock(fakeClock)))
|
||||
ctx := plog.AddZapOverridesToContext(context.Background(), t, &buf, nil, clocktesting.NewFakeClock(now))
|
||||
|
||||
var (
|
||||
gotOptions []oidcclient.Option
|
||||
)
|
||||
ctrl := gomock.NewController(t)
|
||||
t.Cleanup(ctrl.Finish)
|
||||
optionsFactory := mockoidcclientoptions.NewMockOIDCClientOptions(ctrl)
|
||||
if tt.wantOptions != nil {
|
||||
tt.wantOptions(optionsFactory)
|
||||
}
|
||||
|
||||
var gotOptions []oidcclient.Option
|
||||
cmd := oidcLoginCommand(oidcLoginCommandDeps{
|
||||
lookupEnv: func(s string) (string, bool) {
|
||||
v, ok := tt.env[s]
|
||||
@@ -572,6 +373,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
optionsFactory: optionsFactory,
|
||||
})
|
||||
require.NotNil(t, cmd)
|
||||
|
||||
@@ -589,15 +391,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
|
||||
require.Len(t, gotOptions, tt.wantOptionsCount)
|
||||
|
||||
require.Equal(t, tt.wantLogs, logLines(buf.String()))
|
||||
require.Equal(t, tt.wantLogs, testutil.SplitByNewline(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func logLines(logs string) []string {
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(strings.TrimSpace(logs), "\n")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -86,7 +86,7 @@ func staticLoginCommand(deps staticLoginDeps) *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.RunE = func(cmd *cobra.Command, args []string) error { return runStaticLogin(cmd, deps, flags) }
|
||||
cmd.RunE = func(cmd *cobra.Command, _args []string) error { return runStaticLogin(cmd, deps, flags) }
|
||||
|
||||
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
|
||||
mustMarkHidden(cmd, "concierge-namespace")
|
||||
@@ -113,6 +113,7 @@ func runStaticLogin(cmd *cobra.Command, deps staticLoginDeps, flags staticLoginP
|
||||
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
|
||||
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
|
||||
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
|
||||
conciergeclient.WithTransportWrapper(LogAuditIDTransportWrapper),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid Concierge parameters: %w", err)
|
||||
@@ -133,7 +134,7 @@ func runStaticLogin(cmd *cobra.Command, deps staticLoginDeps, flags staticLoginP
|
||||
return fmt.Errorf("--token-env variable %q is empty", flags.staticTokenEnvName)
|
||||
}
|
||||
}
|
||||
cred := tokenCredential(&oidctypes.Token{IDToken: &oidctypes.IDToken{Token: token}})
|
||||
cred := tokenCredential(&oidctypes.IDToken{Token: token})
|
||||
|
||||
// Look up cached credentials based on a hash of all the CLI arguments, the current token value, and the cluster info.
|
||||
cacheKey := struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
clocktesting "k8s.io/utils/clock/testing"
|
||||
@@ -30,7 +29,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
|
||||
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||
require.NoError(t, err)
|
||||
tmpdir := testutil.TempDir(t)
|
||||
tmpdir := t.TempDir()
|
||||
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
||||
require.NoError(t, os.WriteFile(testCABundlePath, testCA.Bundle(), 0600))
|
||||
|
||||
@@ -148,7 +147,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
Error: could not complete Concierge credential exchange: some concierge error
|
||||
`),
|
||||
wantLogs: []string{
|
||||
nowStr + ` pinniped-login cmd/login_static.go:159 exchanging static token for cluster credential {"endpoint": "https://127.0.0.1/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
|
||||
nowStr + ` cmd/login_static.go:160 exchanging static token for cluster credential {"endpoint": "https://127.0.0.1/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -176,11 +175,9 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
fakeClock := clocktesting.NewFakeClock(now)
|
||||
ctx := plog.TestZapOverrides(context.Background(), t, &buf, nil, zap.WithClock(plog.ZapClock(fakeClock)))
|
||||
ctx := plog.AddZapOverridesToContext(context.Background(), t, &buf, nil, clocktesting.NewFakeClock(now))
|
||||
|
||||
cmd := staticLoginCommand(staticLoginDeps{
|
||||
lookupEnv: func(s string) (string, bool) {
|
||||
@@ -218,7 +215,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
require.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
|
||||
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
|
||||
|
||||
require.Equal(t, tt.wantLogs, logLines(buf.String()))
|
||||
require.Equal(t, tt.wantLogs, testutil.SplitByNewline(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
84
cmd/pinniped/cmd/oidc_client_options.go
Normal file
84
cmd/pinniped/cmd/oidc_client_options.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
||||
"go.pinniped.dev/pkg/oidcclient"
|
||||
)
|
||||
|
||||
// OIDCClientOptions is an interface that wraps the creation of Options for the purpose of making them
|
||||
// more friendly to unit tests. Because the Option type refers to a private struct type, it is hard
|
||||
// to create mocks for them in tests of other packages. This provides a seam that can be mocked.
|
||||
// No need for this interface to include deprecated options (such as WithLogger), since those should never be invoked.
|
||||
type OIDCClientOptions interface {
|
||||
WithContext(ctx context.Context) oidcclient.Option
|
||||
WithLoginLogger(logger oidcclient.Logger) oidcclient.Option
|
||||
WithListenPort(port uint16) oidcclient.Option
|
||||
WithSkipBrowserOpen() oidcclient.Option
|
||||
WithSkipListen() oidcclient.Option
|
||||
WithSkipPrintLoginURL() oidcclient.Option
|
||||
WithSessionCache(cache oidcclient.SessionCache) oidcclient.Option
|
||||
WithClient(httpClient *http.Client) oidcclient.Option
|
||||
WithScopes(scopes []string) oidcclient.Option
|
||||
WithRequestAudience(audience string) oidcclient.Option
|
||||
WithLoginFlow(loginFlow v1alpha1.IDPFlow, flowSource string) oidcclient.Option
|
||||
WithUpstreamIdentityProvider(upstreamName, upstreamType string) oidcclient.Option
|
||||
}
|
||||
|
||||
// clientOptions implements OIDCClientOptions for production use.
|
||||
type clientOptions struct{}
|
||||
|
||||
var _ OIDCClientOptions = (*clientOptions)(nil)
|
||||
|
||||
func (o *clientOptions) WithContext(ctx context.Context) oidcclient.Option {
|
||||
return oidcclient.WithContext(ctx)
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithLoginLogger(logger oidcclient.Logger) oidcclient.Option {
|
||||
return oidcclient.WithLoginLogger(logger)
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithListenPort(port uint16) oidcclient.Option {
|
||||
return oidcclient.WithListenPort(port)
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithSkipBrowserOpen() oidcclient.Option {
|
||||
return oidcclient.WithSkipBrowserOpen()
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithSkipListen() oidcclient.Option {
|
||||
return oidcclient.WithSkipListen()
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithSkipPrintLoginURL() oidcclient.Option {
|
||||
return oidcclient.WithSkipPrintLoginURL()
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithSessionCache(cache oidcclient.SessionCache) oidcclient.Option {
|
||||
return oidcclient.WithSessionCache(cache)
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithClient(httpClient *http.Client) oidcclient.Option {
|
||||
return oidcclient.WithClient(httpClient)
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithScopes(scopes []string) oidcclient.Option {
|
||||
return oidcclient.WithScopes(scopes)
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithRequestAudience(audience string) oidcclient.Option {
|
||||
return oidcclient.WithRequestAudience(audience)
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithLoginFlow(loginFlow v1alpha1.IDPFlow, flowSource string) oidcclient.Option {
|
||||
return oidcclient.WithLoginFlow(loginFlow, flowSource)
|
||||
}
|
||||
|
||||
func (o *clientOptions) WithUpstreamIdentityProvider(upstreamName, upstreamType string) oidcclient.Option {
|
||||
return oidcclient.WithUpstreamIdentityProvider(upstreamName, upstreamType)
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/component-base/version"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"go.pinniped.dev/internal/pversion"
|
||||
)
|
||||
|
||||
//nolint:gochecknoinits
|
||||
@@ -15,14 +18,44 @@ func init() {
|
||||
rootCmd.AddCommand(newVersionCommand())
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
output = new(string)
|
||||
// getBuildInfo can be overwritten by tests.
|
||||
getBuildInfo = pversion.Get
|
||||
)
|
||||
|
||||
func newVersionCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "%#v\n", version.Get())
|
||||
return nil
|
||||
},
|
||||
c := &cobra.Command{
|
||||
RunE: runner,
|
||||
Args: cobra.NoArgs, // do not accept positional arguments for this command
|
||||
Use: "version",
|
||||
Short: "Print the version of this Pinniped CLI",
|
||||
}
|
||||
c.Flags().StringVarP(output, "output", "o", "", "one of 'yaml' or 'json'")
|
||||
return c
|
||||
}
|
||||
|
||||
func runner(cmd *cobra.Command, _ []string) error {
|
||||
buildVersion := getBuildInfo()
|
||||
|
||||
switch {
|
||||
case output == nil || *output == "":
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n", buildVersion.GitVersion)
|
||||
case *output == "json":
|
||||
bytes, err := json.MarshalIndent(buildVersion, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n", bytes)
|
||||
case *output == "yaml":
|
||||
bytes, err := yaml.Marshal(buildVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprint(cmd.OutOrStdout(), string(bytes))
|
||||
default:
|
||||
return fmt.Errorf("'%s' is not a valid option for output", *output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
|
||||
"go.pinniped.dev/internal/here"
|
||||
"go.pinniped.dev/internal/pversion"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -19,8 +21,8 @@ var (
|
||||
version \[flags\]
|
||||
|
||||
Flags:
|
||||
-h, --help help for version
|
||||
|
||||
-h, --help help for version
|
||||
-o, --output string one of 'yaml' or 'json'
|
||||
`)
|
||||
|
||||
knownGoodHelpRegexpForVersion = here.Doc(`
|
||||
@@ -30,24 +32,55 @@ var (
|
||||
version \[flags\]
|
||||
|
||||
Flags:
|
||||
-h, --help help for version
|
||||
-h, --help help for version
|
||||
-o, --output string one of 'yaml' or 'json'
|
||||
`)
|
||||
|
||||
emptyVersionRegexp = `version.Info{Major:"", Minor:"", GitVersion:".*", GitCommit:".*", GitTreeState:"", BuildDate:".*", GoVersion:".*", Compiler:".*", Platform:".*/.*"}`
|
||||
jsonRegexp = here.Doc(`{
|
||||
"major": "\d*",
|
||||
"minor": "\d*",
|
||||
"gitVersion": "i am a version for json output",
|
||||
"gitCommit": ".*",
|
||||
"gitTreeState": ".*",
|
||||
"buildDate": ".*",
|
||||
"goVersion": ".*",
|
||||
"compiler": ".*",
|
||||
"platform": ".*/.*"
|
||||
}`)
|
||||
|
||||
yamlRegexp = here.Doc(`buildDate: ".*"
|
||||
compiler: .*
|
||||
gitCommit: .*
|
||||
gitTreeState: .*
|
||||
gitVersion: i am a version for yaml output
|
||||
goVersion: .*
|
||||
major: "\d*"
|
||||
minor: "\d*"
|
||||
platform: .*/.*
|
||||
`)
|
||||
)
|
||||
|
||||
func TestNewVersionCmd(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
getBuildInfo = pversion.Get
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
vars string
|
||||
getBuildInfo func() apimachineryversion.Info
|
||||
wantError bool
|
||||
wantStdoutRegexp string
|
||||
wantStderrRegexp string
|
||||
}{
|
||||
{
|
||||
name: "no flags",
|
||||
args: []string{},
|
||||
wantStdoutRegexp: emptyVersionRegexp + "\n",
|
||||
name: "no flags",
|
||||
args: []string{},
|
||||
getBuildInfo: func() apimachineryversion.Info {
|
||||
return apimachineryversion.Info{GitVersion: "v55.66.44"}
|
||||
},
|
||||
wantStdoutRegexp: "v55.66.44\n",
|
||||
},
|
||||
{
|
||||
name: "help flag passed",
|
||||
@@ -61,10 +94,43 @@ func TestNewVersionCmd(t *testing.T) {
|
||||
wantStderrRegexp: `Error: unknown command "tuna" for "version"`,
|
||||
wantStdoutRegexp: knownGoodUsageRegexpForVersion,
|
||||
},
|
||||
{
|
||||
name: "json output",
|
||||
args: []string{"--output", "json"},
|
||||
getBuildInfo: func() apimachineryversion.Info {
|
||||
return apimachineryversion.Info{
|
||||
GitVersion: "i am a version for json output",
|
||||
Platform: "a/b",
|
||||
}
|
||||
},
|
||||
wantStdoutRegexp: jsonRegexp,
|
||||
},
|
||||
{
|
||||
name: "yaml output",
|
||||
args: []string{"--output", "yaml"},
|
||||
getBuildInfo: func() apimachineryversion.Info {
|
||||
return apimachineryversion.Info{
|
||||
GitVersion: "i am a version for yaml output",
|
||||
Platform: "c/d",
|
||||
}
|
||||
},
|
||||
wantStdoutRegexp: yamlRegexp,
|
||||
},
|
||||
{
|
||||
name: "incorrect output",
|
||||
args: []string{"--output", "foo"},
|
||||
wantError: true,
|
||||
wantStderrRegexp: `Error: 'foo' is not a valid option for output`,
|
||||
wantStdoutRegexp: knownGoodUsageRegexpForVersion,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.getBuildInfo != nil {
|
||||
getBuildInfo = tt.getBuildInfo
|
||||
}
|
||||
|
||||
cmd := newVersionCommand()
|
||||
require.NotNil(t, cmd)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
@@ -24,13 +24,26 @@ import (
|
||||
"go.pinniped.dev/internal/here"
|
||||
)
|
||||
|
||||
type whoamiDeps struct {
|
||||
getenv func(key string) string
|
||||
getClientsets getClientsetsFunc
|
||||
}
|
||||
|
||||
func whoamiRealDeps() whoamiDeps {
|
||||
return whoamiDeps{
|
||||
getenv: os.Getenv,
|
||||
getClientsets: getRealClientsets,
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
rootCmd.AddCommand(newWhoamiCommand(getRealConciergeClientset))
|
||||
rootCmd.AddCommand(newWhoamiCommand(whoamiRealDeps()))
|
||||
}
|
||||
|
||||
type whoamiFlags struct {
|
||||
outputFormat string // e.g., yaml, json, text
|
||||
timeout time.Duration
|
||||
|
||||
kubeconfigPath string
|
||||
kubeconfigContextOverride string
|
||||
@@ -43,7 +56,7 @@ type clusterInfo struct {
|
||||
url string
|
||||
}
|
||||
|
||||
func newWhoamiCommand(getClientset getConciergeClientsetFunc) *cobra.Command {
|
||||
func newWhoamiCommand(deps whoamiDeps) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Args: cobra.NoArgs, // do not accept positional arguments for this command
|
||||
Use: "whoami",
|
||||
@@ -55,20 +68,21 @@ func newWhoamiCommand(getClientset getConciergeClientsetFunc) *cobra.Command {
|
||||
// flags
|
||||
f := cmd.Flags()
|
||||
f.StringVarP(&flags.outputFormat, "output", "o", "text", "Output format (e.g., 'yaml', 'json', 'text')")
|
||||
f.StringVar(&flags.kubeconfigPath, "kubeconfig", os.Getenv("KUBECONFIG"), "Path to kubeconfig file")
|
||||
f.StringVar(&flags.kubeconfigPath, "kubeconfig", deps.getenv("KUBECONFIG"), "Path to kubeconfig file")
|
||||
f.StringVar(&flags.kubeconfigContextOverride, "kubeconfig-context", "", "Kubeconfig context name (default: current active context)")
|
||||
f.StringVar(&flags.apiGroupSuffix, "api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix")
|
||||
f.DurationVar(&flags.timeout, "timeout", 0, "Timeout for the WhoAmI API request (default: 0, meaning no timeout)")
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, _ []string) error {
|
||||
return runWhoami(cmd.OutOrStdout(), getClientset, flags)
|
||||
return runWhoami(cmd.OutOrStdout(), deps, flags)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runWhoami(output io.Writer, getClientset getConciergeClientsetFunc, flags *whoamiFlags) error {
|
||||
func runWhoami(output io.Writer, deps whoamiDeps, flags *whoamiFlags) error {
|
||||
clientConfig := newClientConfig(flags.kubeconfigPath, flags.kubeconfigContextOverride)
|
||||
clientset, err := getClientset(clientConfig, flags.apiGroupSuffix)
|
||||
conciergeClient, _, _, err := deps.getClientsets(clientConfig, flags.apiGroupSuffix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not configure Kubernetes client: %w", err)
|
||||
}
|
||||
@@ -78,12 +92,26 @@ func runWhoami(output io.Writer, getClientset getConciergeClientsetFunc, flags *
|
||||
return fmt.Errorf("could not get current cluster info: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancelFunc()
|
||||
whoAmI, err := clientset.IdentityV1alpha1().WhoAmIRequests().Create(ctx, &identityv1alpha1.WhoAmIRequest{}, metav1.CreateOptions{})
|
||||
// Making the WhoAmI request may cause client-go to invoke the credential plugin, which may
|
||||
// ask the user to interactively authenticate. The time that the user takes to authenticate
|
||||
// is included in the timeout time, but their authentication is not cancelled by exceeding
|
||||
// this timeout. Only the subsequent whoami API request is cancelled. Using a short timeout
|
||||
// causes the odd behavior of a successful login immediately followed by a whoami request failure
|
||||
// due to timeout. For comparison, kubectl uses an infinite timeout by default on API requests
|
||||
// but also allows the user to adjust this timeout with the `--request-timeout` CLI option,
|
||||
// so we will take a similar approach. Note that kubectl has the same behavior when a client-go
|
||||
// credential plugin is invoked and the user takes longer then the timeout to authenticate.
|
||||
ctx := context.Background()
|
||||
if flags.timeout > 0 {
|
||||
var cancelFunc context.CancelFunc
|
||||
ctx, cancelFunc = context.WithTimeout(ctx, flags.timeout)
|
||||
defer cancelFunc()
|
||||
}
|
||||
|
||||
whoAmI, err := conciergeClient.IdentityV1alpha1().WhoAmIRequests().Create(ctx, &identityv1alpha1.WhoAmIRequest{}, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
hint := ""
|
||||
if errors.IsNotFound(err) {
|
||||
if apierrors.IsNotFound(err) {
|
||||
hint = " (is the Pinniped WhoAmI API running and healthy?)"
|
||||
}
|
||||
return fmt.Errorf("could not complete WhoAmIRequest%s: %w", hint, err)
|
||||
|
||||
@@ -1,29 +1,48 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2023-2025 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
kubetesting "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||
|
||||
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
|
||||
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||
fakeconciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake"
|
||||
conciergefake "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned/fake"
|
||||
"go.pinniped.dev/internal/constable"
|
||||
"go.pinniped.dev/internal/here"
|
||||
)
|
||||
|
||||
func TestWhoami(t *testing.T) {
|
||||
helpOutputFormatString := here.Doc(`
|
||||
Print information about the current user
|
||||
|
||||
Usage:
|
||||
whoami [flags]
|
||||
|
||||
Flags:
|
||||
--api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
||||
-h, --help help for whoami
|
||||
--kubeconfig string Path to kubeconfig file%s
|
||||
--kubeconfig-context string Kubeconfig context name (default: current active context)
|
||||
-o, --output string Output format (e.g., 'yaml', 'json', 'text') (default "text")
|
||||
--timeout duration Timeout for the WhoAmI API request (default: 0, meaning no timeout)
|
||||
`)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
env map[string]string
|
||||
groupsOverride []string
|
||||
gettingClientsetErr error
|
||||
callingAPIErr error
|
||||
@@ -31,21 +50,17 @@ func TestWhoami(t *testing.T) {
|
||||
wantStdout, wantStderr string
|
||||
}{
|
||||
{
|
||||
name: "help flag",
|
||||
args: []string{"--help"},
|
||||
wantStdout: here.Doc(`
|
||||
Print information about the current user
|
||||
|
||||
Usage:
|
||||
whoami [flags]
|
||||
|
||||
Flags:
|
||||
--api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
||||
-h, --help help for whoami
|
||||
--kubeconfig string Path to kubeconfig file
|
||||
--kubeconfig-context string Kubeconfig context name (default: current active context)
|
||||
-o, --output string Output format (e.g., 'yaml', 'json', 'text') (default "text")
|
||||
`),
|
||||
name: "help flag passed",
|
||||
args: []string{"--help"},
|
||||
wantStdout: fmt.Sprintf(helpOutputFormatString, ""),
|
||||
},
|
||||
{
|
||||
name: "help flag passed with KUBECONFIG env var set",
|
||||
env: map[string]string{
|
||||
"KUBECONFIG": "/path/to/kubeconfig",
|
||||
},
|
||||
args: []string{"--help"},
|
||||
wantStdout: fmt.Sprintf(helpOutputFormatString, ` (default "/path/to/kubeconfig")`),
|
||||
},
|
||||
{
|
||||
name: "text output",
|
||||
@@ -106,8 +121,7 @@ func TestWhoami(t *testing.T) {
|
||||
Current user info:
|
||||
|
||||
Username: some-username
|
||||
Groups:
|
||||
`),
|
||||
Groups:` + " \n"), // Linters and codeformatters don't like the extra space after "Groups:" and before the newline
|
||||
},
|
||||
{
|
||||
name: "json output",
|
||||
@@ -116,9 +130,7 @@ func TestWhoami(t *testing.T) {
|
||||
{
|
||||
"kind": "WhoAmIRequest",
|
||||
"apiVersion": "identity.concierge.pinniped.dev/v1alpha1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"metadata": {},
|
||||
"spec": {},
|
||||
"status": {
|
||||
"kubernetesUserInfo": {
|
||||
@@ -140,9 +152,7 @@ func TestWhoami(t *testing.T) {
|
||||
{
|
||||
"kind": "WhoAmIRequest",
|
||||
"apiVersion": "identity.concierge.tuna.io/v1alpha1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"metadata": {},
|
||||
"spec": {},
|
||||
"status": {
|
||||
"kubernetesUserInfo": {
|
||||
@@ -163,8 +173,7 @@ func TestWhoami(t *testing.T) {
|
||||
wantStdout: here.Doc(`
|
||||
apiVersion: identity.concierge.pinniped.dev/v1alpha1
|
||||
kind: WhoAmIRequest
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
metadata: {}
|
||||
spec: {}
|
||||
status:
|
||||
kubernetesUserInfo:
|
||||
@@ -181,8 +190,7 @@ func TestWhoami(t *testing.T) {
|
||||
wantStdout: here.Doc(`
|
||||
apiVersion: identity.concierge.tuna.io/v1alpha1
|
||||
kind: WhoAmIRequest
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
metadata: {}
|
||||
spec: {}
|
||||
status:
|
||||
kubernetesUserInfo:
|
||||
@@ -273,20 +281,21 @@ func TestWhoami(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "calling API fails because WhoAmI API is not installed",
|
||||
callingAPIErr: errors.NewNotFound(identityv1alpha1.SchemeGroupVersion.WithResource("whoamirequests").GroupResource(), "whatever"),
|
||||
callingAPIErr: apierrors.NewNotFound(identityv1alpha1.SchemeGroupVersion.WithResource("whoamirequests").GroupResource(), "whatever"),
|
||||
wantError: true,
|
||||
wantStderr: "Error: could not complete WhoAmIRequest (is the Pinniped WhoAmI API running and healthy?): whoamirequests.identity.concierge.pinniped.dev \"whatever\" not found\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
getClientset := func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
|
||||
getClientsetFunc := func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, kubernetes.Interface, aggregatorclient.Interface, error) {
|
||||
if test.gettingClientsetErr != nil {
|
||||
return nil, test.gettingClientsetErr
|
||||
return nil, nil, nil, test.gettingClientsetErr
|
||||
}
|
||||
clientset := fakeconciergeclientset.NewSimpleClientset()
|
||||
clientset.PrependReactor("create", "whoamirequests", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
//nolint:staticcheck // our codegen does not yet generate a NewClientset() function
|
||||
conciergeClient := conciergefake.NewSimpleClientset()
|
||||
conciergeClient.PrependReactor("create", "whoamirequests", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
if test.callingAPIErr != nil {
|
||||
return true, nil, test.callingAPIErr
|
||||
}
|
||||
@@ -305,14 +314,25 @@ func TestWhoami(t *testing.T) {
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return clientset, nil
|
||||
return conciergeClient, nil, nil, nil
|
||||
}
|
||||
cmd := newWhoamiCommand(getClientset)
|
||||
|
||||
cmd := newWhoamiCommand(whoamiDeps{
|
||||
getenv: func(key string) string {
|
||||
return test.env[key]
|
||||
},
|
||||
getClientsets: getClientsetFunc,
|
||||
})
|
||||
|
||||
stdout, stderr := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
|
||||
cmd.SetOut(stdout)
|
||||
cmd.SetErr(stderr)
|
||||
cmd.SetArgs(test.args)
|
||||
if test.args == nil {
|
||||
// cobra uses os.Args[1:] when SetArgs is called with nil, so avoid using nil for tests.
|
||||
cmd.SetArgs([]string{})
|
||||
} else {
|
||||
cmd.SetArgs(test.args)
|
||||
}
|
||||
|
||||
err := cmd.Execute()
|
||||
if test.wantError {
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.20.0
|
||||
name: jwtauthenticators.authentication.concierge.pinniped.dev
|
||||
spec:
|
||||
group: authentication.concierge.pinniped.dev
|
||||
@@ -26,108 +25,366 @@ spec:
|
||||
- jsonPath: .spec.audience
|
||||
name: Audience
|
||||
type: string
|
||||
- jsonPath: .status.phase
|
||||
name: Status
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: "JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
\n Upon receiving a signed JWT, a JWTAuthenticator will performs some validation
|
||||
on it (e.g., valid signature, existence of claims, etc.) and extract the
|
||||
username and groups from the token."
|
||||
description: |-
|
||||
JWTAuthenticator describes the configuration of a JWT authenticator.
|
||||
|
||||
Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid
|
||||
signature, existence of claims, etc.) and extract the username and groups from the token.
|
||||
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'
|
||||
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'
|
||||
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 authenticator.
|
||||
description: spec for configuring the authenticator.
|
||||
properties:
|
||||
audience:
|
||||
description: Audience is the required value of the "aud" JWT claim.
|
||||
description: audience is the required value of the "aud" JWT claim.
|
||||
minLength: 1
|
||||
type: string
|
||||
claimValidationRules:
|
||||
description: |-
|
||||
claimValidationRules are rules that are applied to validate token claims to authenticate users.
|
||||
This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in
|
||||
https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
This is an advanced configuration option. During an end-user login flow, mistakes in this
|
||||
configuration will cause the user's login to fail.
|
||||
items:
|
||||
description: ClaimValidationRule provides the configuration for
|
||||
a single claim validation rule.
|
||||
properties:
|
||||
claim:
|
||||
description: |-
|
||||
claim is the name of a required claim.
|
||||
Only string claim keys are supported.
|
||||
Mutually exclusive with expression and message.
|
||||
type: string
|
||||
expression:
|
||||
description: |-
|
||||
expression represents the expression which will be evaluated by CEL.
|
||||
Must produce a boolean.
|
||||
|
||||
CEL expressions have access to the contents of the token claims, organized into CEL variable:
|
||||
- 'claims' is a map of claim names to claim values.
|
||||
For example, a variable named 'sub' can be accessed as 'claims.sub'.
|
||||
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
|
||||
Must return true for the validation to pass.
|
||||
|
||||
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
|
||||
Mutually exclusive with claim and requiredValue.
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message customizes the returned error message when expression returns false.
|
||||
message is a literal string.
|
||||
Mutually exclusive with claim and requiredValue.
|
||||
type: string
|
||||
requiredValue:
|
||||
description: |-
|
||||
requiredValue is the value of a required claim.
|
||||
Only string claim values are supported.
|
||||
If claim is set and requiredValue is not set, the claim must be present with a value set to the empty string.
|
||||
Mutually exclusive with expression and message.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
claims:
|
||||
description: Claims allows customization of the claims that will be
|
||||
mapped to user identity for Kubernetes access.
|
||||
description: |-
|
||||
claims allows customization of the claims that will be mapped to user identity
|
||||
for Kubernetes access.
|
||||
properties:
|
||||
extra:
|
||||
description: |-
|
||||
extra is similar to claimMappings.extra from Kubernetes AuthenticationConfiguration
|
||||
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
|
||||
However, note that the Pinniped Concierge issues client certificates to users for the purpose
|
||||
of authenticating, and the Kubernetes API server does not have any mechanism for transmitting
|
||||
auth extras via client certificates. When configured, these extras will appear in client
|
||||
certificates issued by the Pinniped Supervisor in the x509 Subject field as Organizational
|
||||
Units (OU). However, when this client certificate is presented to Kubernetes for authentication,
|
||||
Kubernetes will ignore these extras. This is probably only useful if you are using a custom
|
||||
authenticating proxy in front of your Kubernetes API server which can translate these OUs into
|
||||
auth extras, as described by
|
||||
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy.
|
||||
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
|
||||
must evaluate to either a string or an array of strings, or else the user's login will fail.
|
||||
|
||||
These keys must be a domain-prefixed path (such as "acme.io/foo") and must not contain an equals sign ("=").
|
||||
|
||||
expression must produce a string or string array value.
|
||||
If the value is empty, the extra mapping will not be present.
|
||||
|
||||
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
|
||||
hard-coded extra key/value
|
||||
- key: "acme.io/foo"
|
||||
valueExpression: "'bar'"
|
||||
This will result in an extra attribute - acme.io/foo: ["bar"]
|
||||
|
||||
hard-coded key, value copying claim value
|
||||
- key: "acme.io/foo"
|
||||
valueExpression: "claims.some_claim"
|
||||
This will result in an extra attribute - acme.io/foo: [value of some_claim]
|
||||
|
||||
hard-coded key, value derived from claim value
|
||||
- key: "acme.io/admin"
|
||||
valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""'
|
||||
This will result in:
|
||||
- if is_admin claim is present and true, extra attribute - acme.io/admin: ["true"]
|
||||
- if is_admin claim is present and false or is_admin claim is not present, no extra attribute will be added
|
||||
items:
|
||||
description: ExtraMapping provides the configuration for a single
|
||||
extra mapping.
|
||||
properties:
|
||||
key:
|
||||
description: |-
|
||||
key is a string to use as the extra attribute key.
|
||||
key must be a domain-prefix path (e.g. example.org/foo). All characters before the first "/" must be a valid
|
||||
subdomain as defined by RFC 1123. All characters trailing the first "/" must
|
||||
be valid HTTP Path characters as defined by RFC 3986.
|
||||
key must be lowercase.
|
||||
Required to be unique.
|
||||
Additionally, the key must not contain an equals sign ("=").
|
||||
type: string
|
||||
valueExpression:
|
||||
description: |-
|
||||
valueExpression is a CEL expression to extract extra attribute value.
|
||||
valueExpression must produce a string or string array value.
|
||||
"", [], and null values are treated as the extra mapping not being present.
|
||||
Empty string values contained within a string array are filtered out.
|
||||
|
||||
CEL expressions have access to the contents of the token claims, organized into CEL variable:
|
||||
- 'claims' is a map of claim names to claim values.
|
||||
For example, a variable named 'sub' can be accessed as 'claims.sub'.
|
||||
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
|
||||
|
||||
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- valueExpression
|
||||
type: object
|
||||
type: array
|
||||
groups:
|
||||
description: Groups is the name of the claim which should be read
|
||||
to extract the user's group membership from the JWT token. When
|
||||
not specified, it will default to "groups".
|
||||
description: |-
|
||||
groups is the name of the claim which should be read to extract the user's
|
||||
group membership from the JWT token. When not specified, it will default to "groups",
|
||||
unless groupsExpression is specified.
|
||||
|
||||
Mutually exclusive with groupsExpression. Use either groups or groupsExpression to
|
||||
determine the user's group membership from the JWT token.
|
||||
type: string
|
||||
groupsExpression:
|
||||
description: |-
|
||||
groupsExpression represents an expression which will be evaluated by CEL.
|
||||
The expression's result will become the user's group memberships.
|
||||
|
||||
groupsExpression is similar to claimMappings.groups.expression from Kubernetes AuthenticationConfiguration
|
||||
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
|
||||
must evaluate to one of the expected types without errors, or else the user's login will fail.
|
||||
Additionally, mistakes in this configuration can cause the users to have unintended group memberships.
|
||||
|
||||
The expression must produce a string or string array value.
|
||||
"", [], and null values are treated as the group mapping not being present.
|
||||
|
||||
CEL expressions have access to the contents of the token claims, organized into CEL variable:
|
||||
- 'claims' is a map of claim names to claim values.
|
||||
For example, a variable named 'sub' can be accessed as 'claims.sub'.
|
||||
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
|
||||
|
||||
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
|
||||
Mutually exclusive with groups. Use either groups or groupsExpression to
|
||||
determine the user's group membership from the JWT token.
|
||||
type: string
|
||||
username:
|
||||
description: Username is the name of the claim which should be
|
||||
read to extract the username from the JWT token. When not specified,
|
||||
it will default to "username".
|
||||
description: |-
|
||||
username is the name of the claim which should be read to extract the
|
||||
username from the JWT token. When not specified, it will default to "username",
|
||||
unless usernameExpression is specified.
|
||||
|
||||
Mutually exclusive with usernameExpression. Use either username or usernameExpression to
|
||||
determine the user's username from the JWT token.
|
||||
type: string
|
||||
usernameExpression:
|
||||
description: |-
|
||||
usernameExpression represents an expression which will be evaluated by CEL.
|
||||
The expression's result will become the user's username.
|
||||
|
||||
usernameExpression is similar to claimMappings.username.expression from Kubernetes AuthenticationConfiguration
|
||||
as documented in https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
This is an advanced configuration option. During an end-user login flow, each of these CEL expressions
|
||||
must evaluate to the expected type without errors, or else the user's login will fail.
|
||||
Additionally, mistakes in this configuration can cause the users to have unintended usernames.
|
||||
|
||||
The expression must produce a non-empty string value.
|
||||
If the expression uses 'claims.email', then 'claims.email_verified' must be used in
|
||||
the expression or extra[*].valueExpression or claimValidationRules[*].expression.
|
||||
An example claim validation rule expression that matches the validation automatically
|
||||
applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
|
||||
By explicitly comparing the value to true, we let type-checking see the result will be a boolean,
|
||||
and to make sure a non-boolean email_verified claim will be caught at runtime.
|
||||
|
||||
CEL expressions have access to the contents of the token claims, organized into CEL variable:
|
||||
- 'claims' is a map of claim names to claim values.
|
||||
For example, a variable named 'sub' can be accessed as 'claims.sub'.
|
||||
Nested claims can be accessed using dot notation, e.g. 'claims.foo.bar'.
|
||||
|
||||
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
|
||||
Mutually exclusive with username. Use either username or usernameExpression to
|
||||
determine the user's username from the JWT token.
|
||||
type: string
|
||||
type: object
|
||||
issuer:
|
||||
description: Issuer is the OIDC issuer URL that will be used to discover
|
||||
public signing keys. Issuer is also used to validate the "iss" JWT
|
||||
claim.
|
||||
description: |-
|
||||
issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is
|
||||
also used to validate the "iss" JWT claim.
|
||||
minLength: 1
|
||||
pattern: ^https://
|
||||
type: string
|
||||
tls:
|
||||
description: TLS configuration for communicating with the OIDC provider.
|
||||
description: tls is the configuration for communicating with the OIDC
|
||||
provider via TLS.
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: X.509 Certificate Authority (base64-encoded PEM bundle).
|
||||
If omitted, a default set of system roots will be trusted.
|
||||
type: string
|
||||
certificateAuthorityDataSource:
|
||||
description: |-
|
||||
Reference to a CA bundle in a secret or a configmap.
|
||||
Any changes to the CA bundle in the secret or configmap will be dynamically reloaded.
|
||||
properties:
|
||||
key:
|
||||
description: |-
|
||||
Key is the key name within the secret or configmap from which to read the CA bundle.
|
||||
The value found at this key in the secret or configmap must not be empty, and must be a valid PEM-encoded
|
||||
certificate bundle.
|
||||
minLength: 1
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind configures whether the CA bundle is being sourced from a Kubernetes secret or a configmap.
|
||||
Allowed values are "Secret" or "ConfigMap".
|
||||
"ConfigMap" uses a Kubernetes configmap to source CA Bundles.
|
||||
"Secret" uses Kubernetes secrets of type kubernetes.io/tls or Opaque to source CA Bundles.
|
||||
enum:
|
||||
- Secret
|
||||
- ConfigMap
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name is the resource name of the secret or configmap from which to read the CA bundle.
|
||||
The referenced secret or configmap must be created in the same namespace where Pinniped Concierge is installed.
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
userValidationRules:
|
||||
description: |-
|
||||
userValidationRules are rules that are applied to final user before completing authentication.
|
||||
These allow invariants to be applied to incoming identities such as preventing the
|
||||
use of the system: prefix that is commonly used by Kubernetes components.
|
||||
The validation rules are logically ANDed together and must all return true for the validation to pass.
|
||||
This is similar to claimValidationRules from Kubernetes AuthenticationConfiguration as documented in
|
||||
https://kubernetes.io/docs/reference/access-authn-authz/authentication.
|
||||
This is an advanced configuration option. During an end-user login flow, mistakes in this
|
||||
configuration will cause the user's login to fail.
|
||||
items:
|
||||
description: UserValidationRule provides the configuration for a
|
||||
single user info validation rule.
|
||||
properties:
|
||||
expression:
|
||||
description: |-
|
||||
expression represents the expression which will be evaluated by CEL.
|
||||
Must return true for the validation to pass.
|
||||
|
||||
CEL expressions have access to the contents of UserInfo, organized into CEL variable:
|
||||
- 'user' - authentication.k8s.io/v1, Kind=UserInfo object
|
||||
Refer to https://github.com/kubernetes/api/blob/release-1.28/authentication/v1/types.go#L105-L122 for the definition.
|
||||
API documentation: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#userinfo-v1-authentication-k8s-io
|
||||
|
||||
Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message customizes the returned error message when rule returns false.
|
||||
message is a literal string.
|
||||
type: string
|
||||
required:
|
||||
- expression
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- audience
|
||||
- issuer
|
||||
type: object
|
||||
status:
|
||||
description: Status of the authenticator.
|
||||
description: status of the authenticator.
|
||||
properties:
|
||||
conditions:
|
||||
description: Represents the observations of the authenticator'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.
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
@@ -142,10 +399,6 @@ spec:
|
||||
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
|
||||
@@ -160,6 +413,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the JWTAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
@@ -168,9 +429,3 @@ spec:
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.20.0
|
||||
name: webhookauthenticators.authentication.concierge.pinniped.dev
|
||||
spec:
|
||||
group: authentication.concierge.pinniped.dev
|
||||
@@ -23,6 +22,9 @@ spec:
|
||||
- jsonPath: .spec.endpoint
|
||||
name: Endpoint
|
||||
type: string
|
||||
- jsonPath: .status.phase
|
||||
name: Status
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
@@ -33,14 +35,19 @@ spec:
|
||||
authenticator.
|
||||
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'
|
||||
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'
|
||||
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
|
||||
@@ -59,6 +66,39 @@ spec:
|
||||
description: X.509 Certificate Authority (base64-encoded PEM bundle).
|
||||
If omitted, a default set of system roots will be trusted.
|
||||
type: string
|
||||
certificateAuthorityDataSource:
|
||||
description: |-
|
||||
Reference to a CA bundle in a secret or a configmap.
|
||||
Any changes to the CA bundle in the secret or configmap will be dynamically reloaded.
|
||||
properties:
|
||||
key:
|
||||
description: |-
|
||||
Key is the key name within the secret or configmap from which to read the CA bundle.
|
||||
The value found at this key in the secret or configmap must not be empty, and must be a valid PEM-encoded
|
||||
certificate bundle.
|
||||
minLength: 1
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind configures whether the CA bundle is being sourced from a Kubernetes secret or a configmap.
|
||||
Allowed values are "Secret" or "ConfigMap".
|
||||
"ConfigMap" uses a Kubernetes configmap to source CA Bundles.
|
||||
"Secret" uses Kubernetes secrets of type kubernetes.io/tls or Opaque to source CA Bundles.
|
||||
enum:
|
||||
- Secret
|
||||
- ConfigMap
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name is the resource name of the secret or configmap from which to read the CA bundle.
|
||||
The referenced secret or configmap must be created in the same namespace where Pinniped Concierge is installed.
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- endpoint
|
||||
@@ -70,37 +110,35 @@ spec:
|
||||
description: Represents the observations of the authenticator'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.
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
@@ -115,10 +153,6 @@ spec:
|
||||
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
|
||||
@@ -133,6 +167,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
@@ -141,9 +183,3 @@ spec:
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.20.0
|
||||
name: credentialissuers.config.concierge.pinniped.dev
|
||||
spec:
|
||||
group: config.concierge.pinniped.dev
|
||||
@@ -34,14 +33,19 @@ spec:
|
||||
Pinniped Concierge credential issuer.
|
||||
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'
|
||||
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'
|
||||
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
|
||||
@@ -53,18 +57,18 @@ spec:
|
||||
of the Concierge impersonation proxy.
|
||||
properties:
|
||||
externalEndpoint:
|
||||
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.type
|
||||
is \"None\"."
|
||||
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.
|
||||
|
||||
This field must be non-empty when spec.impersonationProxy.service.type is "None".
|
||||
type: string
|
||||
mode:
|
||||
description: '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.'
|
||||
description: |-
|
||||
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.
|
||||
enum:
|
||||
- auto
|
||||
- enabled
|
||||
@@ -83,26 +87,43 @@ spec:
|
||||
pairs to set as annotations on the provisioned Service.
|
||||
type: object
|
||||
loadBalancerIP:
|
||||
description: LoadBalancerIP specifies the IP address to set
|
||||
in the spec.loadBalancerIP field of the provisioned Service.
|
||||
description: |-
|
||||
LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service.
|
||||
This is not supported on all cloud providers.
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
type: string
|
||||
type:
|
||||
default: LoadBalancer
|
||||
description: "Type specifies the type of Service to provision
|
||||
for the impersonation proxy. \n 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."
|
||||
description: |-
|
||||
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.
|
||||
enum:
|
||||
- LoadBalancer
|
||||
- ClusterIP
|
||||
- None
|
||||
type: string
|
||||
type: object
|
||||
tls:
|
||||
description: |-
|
||||
TLS contains information about how the Concierge impersonation proxy should serve TLS.
|
||||
|
||||
If this field is empty, the impersonation proxy will generate its own TLS certificate.
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: |-
|
||||
X.509 Certificate Authority (base64-encoded PEM bundle).
|
||||
Used to advertise the CA bundle for the impersonation proxy endpoint.
|
||||
type: string
|
||||
secretName:
|
||||
description: |-
|
||||
SecretName is the name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains
|
||||
the TLS serving certificate for the Concierge impersonation proxy endpoint.
|
||||
minLength: 1
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- mode
|
||||
- service
|
||||
@@ -113,24 +134,6 @@ spec:
|
||||
status:
|
||||
description: CredentialIssuerStatus describes the status of the Concierge.
|
||||
properties:
|
||||
kubeConfigInfo:
|
||||
description: Information needed to form a valid Pinniped-based kubeconfig
|
||||
using this credential issuer. This field is deprecated and will
|
||||
be removed in a future version.
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: The K8s API server CA bundle.
|
||||
minLength: 1
|
||||
type: string
|
||||
server:
|
||||
description: The K8s API server URL.
|
||||
minLength: 1
|
||||
pattern: ^https://|^http://
|
||||
type: string
|
||||
required:
|
||||
- certificateAuthorityData
|
||||
- server
|
||||
type: object
|
||||
strategies:
|
||||
description: List of integration strategies that were attempted by
|
||||
Pinniped.
|
||||
@@ -143,9 +146,9 @@ spec:
|
||||
this strategy.
|
||||
properties:
|
||||
impersonationProxyInfo:
|
||||
description: ImpersonationProxyInfo describes the parameters
|
||||
for the impersonation proxy on this Concierge. This field
|
||||
is only set when Type is "ImpersonationProxy".
|
||||
description: |-
|
||||
ImpersonationProxyInfo describes the parameters for the impersonation proxy on this Concierge.
|
||||
This field is only set when Type is "ImpersonationProxy".
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: CertificateAuthorityData is the base64-encoded
|
||||
@@ -163,9 +166,9 @@ spec:
|
||||
- endpoint
|
||||
type: object
|
||||
tokenCredentialRequestInfo:
|
||||
description: TokenCredentialRequestAPIInfo describes the
|
||||
parameters for the TokenCredentialRequest API on this
|
||||
Concierge. This field is only set when Type is "TokenCredentialRequestAPI".
|
||||
description: |-
|
||||
TokenCredentialRequestAPIInfo describes the parameters for the TokenCredentialRequest API on this Concierge.
|
||||
This field is only set when Type is "TokenCredentialRequestAPI".
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: CertificateAuthorityData is the base64-encoded
|
||||
@@ -238,9 +241,3 @@ spec:
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
#! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2025 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", "deploymentPodLabel", "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:
|
||||
@@ -45,8 +54,9 @@ metadata:
|
||||
annotations:
|
||||
#! we need to create this service account before we create the secret
|
||||
kapp.k14s.io/change-group: "impersonation-proxy.concierge.pinniped.dev/serviceaccount"
|
||||
secrets: #! make sure the token controller does not create any other secrets
|
||||
- name: #@ defaultResourceNameWithSuffix("impersonation-proxy")
|
||||
kubernetes.io/enforce-mountable-secrets: "true"
|
||||
secrets: [] #! make sure the token controller does not create any secrets
|
||||
automountServiceAccountToken: false
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
@@ -67,6 +77,7 @@ data:
|
||||
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
|
||||
aggregatedAPIServerDisableAdmissionPlugins: []
|
||||
names:
|
||||
servingCertificateSecret: (@= defaultResourceNameWithSuffix("api-tls-serving-certificate") @)
|
||||
credentialIssuer: (@= defaultResourceNameWithSuffix("config") @)
|
||||
@@ -77,9 +88,12 @@ data:
|
||||
impersonationCACertificateSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-ca-certificate") @)
|
||||
impersonationSignerSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-signer-ca-certificate") @)
|
||||
agentServiceAccount: (@= defaultResourceNameWithSuffix("kube-cert-agent") @)
|
||||
impersonationProxyServiceAccount: (@= defaultResourceNameWithSuffix("impersonation-proxy") @)
|
||||
impersonationProxyLegacySecret: (@= defaultResourceNameWithSuffix("impersonation-proxy") @)
|
||||
labels: (@= json.encode(labels()).rstrip() @)
|
||||
kubeCertAgent:
|
||||
namePrefix: (@= defaultResourceNameWithSuffix("kube-cert-agent-") @)
|
||||
priorityClassName: (@= data.values.kube_cert_agent_priority_class_name @)
|
||||
(@ if data.values.kube_cert_agent_image: @)
|
||||
image: (@= data.values.kube_cert_agent_image @)
|
||||
(@ else: @)
|
||||
@@ -91,17 +105,17 @@ data:
|
||||
(@ end @)
|
||||
(@ if data.values.image_pull_dockerconfigjson: @)
|
||||
imagePullSecrets:
|
||||
- image-pull-secret
|
||||
- image-pull-secret
|
||||
(@ end @)
|
||||
(@ if data.values.log_level or data.values.deprecated_log_format: @)
|
||||
(@ if data.values.log_level: @)
|
||||
log:
|
||||
(@ if data.values.log_level: @)
|
||||
level: (@= getAndValidateLogLevel() @)
|
||||
(@ end @)
|
||||
(@ if data.values.deprecated_log_format: @)
|
||||
format: (@= data.values.deprecated_log_format @)
|
||||
(@ end @)
|
||||
(@ end @)
|
||||
tls:
|
||||
onedottwo:
|
||||
allowedCiphers: (@= str(data.values.allowed_ciphers_for_tls_onedottwo) @)
|
||||
audit:
|
||||
logUsernamesAndGroups: (@= data.values.audit.log_usernames_and_groups @)
|
||||
---
|
||||
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
|
||||
apiVersion: v1
|
||||
@@ -134,8 +148,6 @@ spec:
|
||||
#! 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:
|
||||
securityContext:
|
||||
runAsUser: #@ data.values.run_as_user
|
||||
@@ -184,9 +196,6 @@ spec:
|
||||
- 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
|
||||
@@ -222,12 +231,6 @@ spec:
|
||||
- name: config-volume
|
||||
configMap:
|
||||
name: #@ defaultResourceNameWithSuffix("config")
|
||||
- name: impersonation-proxy
|
||||
secret:
|
||||
secretName: #@ defaultResourceNameWithSuffix("impersonation-proxy")
|
||||
items: #! make sure our pod does not start until the token controller has a chance to populate the secret
|
||||
- key: token
|
||||
path: token
|
||||
- name: podinfo
|
||||
downwardAPI:
|
||||
items:
|
||||
@@ -247,9 +250,14 @@ spec:
|
||||
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).
|
||||
#!priorityClassName: system-cluster-critical
|
||||
- key: kubernetes.io/arch
|
||||
effect: NoSchedule
|
||||
operator: Equal
|
||||
value: amd64 #! Allow running on amd64 nodes.
|
||||
- key: kubernetes.io/arch
|
||||
effect: NoSchedule
|
||||
operator: Equal
|
||||
value: arm64 #! Also allow running on arm64 nodes.
|
||||
#! This will help make sure our multiple pods run on different nodes, making
|
||||
#! our deployment "more" "HA".
|
||||
affinity:
|
||||
@@ -344,17 +352,9 @@ spec:
|
||||
#@ if data.values.impersonation_proxy_spec.service.load_balancer_ip:
|
||||
loadBalancerIP: #@ data.values.impersonation_proxy_spec.service.load_balancer_ip
|
||||
#@ end
|
||||
#@ if data.values.impersonation_proxy_spec.service.annotations == None:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "4000"
|
||||
#@ else:
|
||||
annotations: #@ data.values.impersonation_proxy_spec.service.annotations
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("impersonation-proxy")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
annotations:
|
||||
#! wait until the SA exists to create this secret so that the token controller does not delete it
|
||||
#! we have this secret at the end so that kubectl will create the service account first
|
||||
kapp.k14s.io/change-rule: "upsert after upserting impersonation-proxy.concierge.pinniped.dev/serviceaccount"
|
||||
kubernetes.io/service-account.name: #@ defaultResourceNameWithSuffix("impersonation-proxy")
|
||||
type: kubernetes.io/service-account-token
|
||||
#@ end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
@@ -19,7 +19,7 @@ rules:
|
||||
resources: [ apiservices ]
|
||||
verbs: [ get, list, patch, update, watch ]
|
||||
- apiGroups: [ admissionregistration.k8s.io ]
|
||||
resources: [ validatingwebhookconfigurations, mutatingwebhookconfigurations ]
|
||||
resources: [ validatingwebhookconfigurations, mutatingwebhookconfigurations, validatingadmissionpolicies, validatingadmissionpolicybindings ]
|
||||
verbs: [ get, list, watch ]
|
||||
- apiGroups: [ flowcontrol.apiserver.k8s.io ]
|
||||
resources: [ flowschemas, prioritylevelconfigurations ]
|
||||
@@ -43,6 +43,10 @@ rules:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
|
||||
resources: [ jwtauthenticators, webhookauthenticators ]
|
||||
verbs: [ get, list, watch ]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
|
||||
resources: [ jwtauthenticators/status, webhookauthenticators/status ]
|
||||
verbs: [ get, list, watch, update ]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -156,6 +160,13 @@ rules:
|
||||
- apiGroups: [ coordination.k8s.io ]
|
||||
resources: [ leases ]
|
||||
verbs: [ create, get, update ]
|
||||
#! We need to be able to get service accounts and create serviceaccounts/tokens so that we can create short-lived tokens for the impersonation proxy
|
||||
- apiGroups: [""]
|
||||
resources: [ serviceaccounts ]
|
||||
verbs: [ get ]
|
||||
- apiGroups: [""]
|
||||
resources: [ serviceaccounts/token ]
|
||||
verbs: [ create ]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
||||
@@ -1,107 +1,254 @@
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@data/values
|
||||
---
|
||||
#@ def validate_strings_map(obj):
|
||||
#@ # Returns True if obj is an associative data structure string→string, and False otherwise.
|
||||
#@ for key in obj:
|
||||
#@ if type(key) != "string" or type(obj[key]) != "string":
|
||||
#@ return False
|
||||
#@ end
|
||||
#@ end
|
||||
#@ return True
|
||||
#@ end
|
||||
|
||||
#@data/values-schema
|
||||
---
|
||||
#@schema/title "App name"
|
||||
#@schema/desc "Used to help determine the names of various resources and labels."
|
||||
#@schema/validation min_len=1
|
||||
app_name: pinniped-concierge
|
||||
|
||||
#! Creates a new namespace statically in yaml with the given name and installs the app into that namespace.
|
||||
#@schema/title "Namespace"
|
||||
#@schema/desc "Creates a new namespace statically in yaml with the given name and installs the app into that namespace."
|
||||
#@schema/validation min_len=1
|
||||
namespace: pinniped-concierge
|
||||
#! If specified, assumes that a namespace of the given name already exists and installs the app into that namespace.
|
||||
#! If both `namespace` and `into_namespace` are specified, then only `into_namespace` is used.
|
||||
into_namespace: #! e.g. my-preexisting-namespace
|
||||
|
||||
#! All resources created statically by yaml at install-time and all resources created dynamically
|
||||
#! by controllers at runtime will be labelled with `app: $app_name` and also with the labels
|
||||
#! specified here. The value of `custom_labels` must be a map of string keys to string values.
|
||||
#! The app can be uninstalled either by:
|
||||
#! 1. Deleting the static install-time yaml resources including the static namespace, which will cascade and also delete
|
||||
#! resources that were dynamically created by controllers at runtime
|
||||
#! 2. Or, deleting all resources by label, which does not assume that there was a static install-time yaml namespace.
|
||||
custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLabelName: otherCustomLabelValue}
|
||||
#@schema/title "Into namespace"
|
||||
#@ into_namespace_desc = "If specified, assumes that a namespace of the given name already exists and installs the app into that namespace. \
|
||||
#@ If both `namespace` and `into_namespace` are specified, then only `into_namespace` is used."
|
||||
#@schema/desc into_namespace_desc
|
||||
#@schema/examples ("The name of an existing namespace", "my-preexisting-namespace")
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1
|
||||
into_namespace: ""
|
||||
|
||||
#! Specify how many replicas of the Pinniped server to run.
|
||||
#@schema/title "Custom labels"
|
||||
#@ custom_labels_desc = "All resources created statically by yaml at install-time and all resources created dynamically \
|
||||
#@ by controllers at runtime will be labelled with `app: $app_name` and also with the labels specified here. The value of \
|
||||
#@ `custom_labels` must be a map of string keys to string values. The app can be uninstalled either by: 1.) deleting the \
|
||||
#@ static install-time yaml resources including the static namespace, which will cascade and also delete \
|
||||
#@ resources that were dynamically created by controllers at runtime, or 2.) deleting all resources by label, which does \
|
||||
#@ not assume that there was a static install-time yaml namespace."
|
||||
#@schema/desc custom_labels_desc
|
||||
#@schema/examples ("Example set of labels", {"myCustomLabelName": "myCustomLabelValue", "otherCustomLabelName": "otherCustomLabelValue"})
|
||||
#@schema/type any=True
|
||||
#@schema/validation ("a map of string keys and string values", validate_strings_map)
|
||||
custom_labels: { }
|
||||
|
||||
#@schema/title "Replicas"
|
||||
#@schema/desc "Specify how many replicas of the Pinniped server to run."
|
||||
replicas: 2
|
||||
|
||||
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
|
||||
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
|
||||
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
|
||||
#@schema/title "Image repo"
|
||||
#@schema/desc "The repository for the Concierge container image."
|
||||
#@schema/validation min_len=1
|
||||
image_repo: ghcr.io/vmware/pinniped/pinniped-server
|
||||
|
||||
#@schema/title "Image digest"
|
||||
#@schema/desc "The image digest for the Concierge container image. If both image_digest or an image_tag are given, only image_digest will be used."
|
||||
#@schema/examples ("Providing a digest", "sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8")
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1, when=lambda _, ctx: ctx.parent["image_tag"] == None
|
||||
image_digest: ""
|
||||
|
||||
#@schema/title "Image tag"
|
||||
#@schema/desc "The image tag for the Concierge container image. If both image_digest or an image_tag are given, only image_digest will be used."
|
||||
#@schema/examples ("Providing a tag", "v0.25.0")
|
||||
#@schema/validation min_len=1, when=lambda _, ctx: ctx.parent["image_digest"] == None
|
||||
image_tag: latest
|
||||
|
||||
#! Optionally specify a different image for the "kube-cert-agent" pod which is scheduled
|
||||
#! on the control plane. This image needs only to include `sleep` and `cat` binaries.
|
||||
#! By default, the same image specified for image_repo/image_digest/image_tag will be re-used.
|
||||
kube_cert_agent_image:
|
||||
#@schema/title "Kube Cert Agent image"
|
||||
#@ kube_cert_agent_image_desc = "Optionally specify a different image for the 'kube-cert-agent' pod which is scheduled \
|
||||
#@ on the control plane. This image needs only to include `sleep` and `cat` binaries. \
|
||||
#@ By default, the same image specified for image_repo/image_digest/image_tag will be re-used."
|
||||
#@schema/desc kube_cert_agent_image_desc
|
||||
#@schema/examples ("Image including tag or digest", "ghcr.io/vmware/pinniped/pinniped-server:latest")
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1
|
||||
kube_cert_agent_image: ""
|
||||
|
||||
#! Specifies a secret to be used when pulling the above `image_repo` container image.
|
||||
#! Can be used when the above image_repo is a private registry.
|
||||
#! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]'
|
||||
#! Optional.
|
||||
image_pull_dockerconfigjson: #! e.g. {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}
|
||||
#@schema/title "Kube Cert Agent Priority Class Name"
|
||||
#@ kube_cert_agent_priority_class_name_desc = "Optionally specify a PriorityClassName for the 'kube-cert-agent' pod. \
|
||||
#@ See https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ for more details. \
|
||||
#@ By default, this is the empty string."
|
||||
#@schema/desc kube_cert_agent_priority_class_name_desc
|
||||
#@schema/examples ("name of a PriorityClass object", "high-priority")
|
||||
#@schema/validation min_len=0
|
||||
kube_cert_agent_priority_class_name: ""
|
||||
|
||||
#! Pinniped will try to guess the right K8s API URL for sharing that information with potential clients.
|
||||
#! This setting allows the guess to be overridden.
|
||||
#! Optional.
|
||||
discovery_url: #! e.g., https://example.com
|
||||
#@schema/title "Image pull dockerconfigjson"
|
||||
#@ image_pull_dockerconfigjson_desc = "A base64 encoded secret to be used when pulling the `image_repo` container image. \
|
||||
#@ Can be used when the image_repo is a private registry. Typically, the value would be the output of: \
|
||||
#@ kubectl create secret docker-registry x --docker-server=https://example.io --docker-username='USERNAME' --docker-password='PASSWORD' --dry-run=client -o json | jq -r '.data[\".dockerconfigjson\"]'"
|
||||
#@schema/desc image_pull_dockerconfigjson_desc
|
||||
#@ example_desc = 'base64 encoding of: {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}'
|
||||
#@ example_value = "eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUuaW8iOnsidXNlcm5hbWUiOiJVU0VSTkFNRSIsInBhc3N3b3JkIjoiUEFTU1dPUkQiLCJhdXRoIjoiVlZORlVrNUJUVVU2VUVGVFUxZFBVa1E9In19fQ=="
|
||||
#@schema/examples (example_desc, example_value)
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1
|
||||
image_pull_dockerconfigjson: ""
|
||||
|
||||
#! Specify the duration and renewal interval for the API serving certificate.
|
||||
#! The defaults are set to expire the cert about every 30 days, and to rotate it
|
||||
#! about every 25 days.
|
||||
#@schema/title "Discovery URL"
|
||||
#@schema/desc "Pinniped will try to guess the right K8s API URL for sharing that information with potential clients. This setting allows the guess to be overridden."
|
||||
#@schema/examples ("Kubernetes API URL","https://example.com")
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1
|
||||
discovery_url: ""
|
||||
|
||||
#@schema/title "API serving certificate duration seconds"
|
||||
#@ api_serving_certificate_duration_seconds_desc = "Specify the duration for the API serving certificate. \
|
||||
#@ The default is set to expire the cert about every 30 days. \
|
||||
#@ Specify this as an integer or as a string which contains an integer value."
|
||||
#@schema/desc api_serving_certificate_duration_seconds_desc
|
||||
#@schema/type any=True
|
||||
#@schema/validation ("an int or string which contains an integer value", lambda v: type(v) in ["int", "string"])
|
||||
api_serving_certificate_duration_seconds: 2592000
|
||||
|
||||
#@schema/title "API serving certificate renew before seconds"
|
||||
#@ api_serving_certificate_renew_before_seconds_desc = "Specify the renewal interval for the API serving certificate. \
|
||||
#@ The default is set to rotate it about every 25 days. \
|
||||
#@ Specify this as an integer or as a string which contains an integer value."
|
||||
#@schema/desc api_serving_certificate_renew_before_seconds_desc
|
||||
#@schema/type any=True
|
||||
#@schema/validation ("an int or string which contains an integer value", lambda v: type(v) in ["int", "string"])
|
||||
api_serving_certificate_renew_before_seconds: 2160000
|
||||
|
||||
#! Specify the verbosity of logging: info ("nice to know" information), debug (developer
|
||||
#! 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.
|
||||
#! Specify the format of logging: json (for machine parsable logs) and text (for legacy klog formatted logs).
|
||||
#! By default, when this value is left unset, logs are formatted in json.
|
||||
#! This configuration is deprecated and will be removed in a future release at which point logs will always be formatted as json.
|
||||
deprecated_log_format:
|
||||
#@schema/title "Log level"
|
||||
#@ log_level_desc = "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. \
|
||||
#@ When this value is left unset, only warnings and errors are printed. There is no way to suppress warning and error logs."
|
||||
#@schema/desc log_level_desc
|
||||
#@schema/examples ("Developer logging information","debug")
|
||||
#@schema/nullable
|
||||
#@schema/validation one_of=["info", "debug", "trace", "all"]
|
||||
log_level: ""
|
||||
|
||||
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
|
||||
#@schema/title "Run as user"
|
||||
#@schema/desc "The user ID that will own the process."
|
||||
#! See the Dockerfile for the reasoning behind this default value.
|
||||
run_as_user: 65532
|
||||
|
||||
#! 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.
|
||||
#@schema/title "Run as group"
|
||||
#@schema/desc "The group ID that will own the process."
|
||||
#! See the Dockerfile for the reasoning behind this default value.
|
||||
run_as_group: 65532
|
||||
|
||||
#@schema/title "API group suffix"
|
||||
#@ api_group_suffix_desc = "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."
|
||||
#@schema/desc api_group_suffix_desc
|
||||
#@schema/validation min_len=1
|
||||
api_group_suffix: pinniped.dev
|
||||
|
||||
#! Customize CredentialIssuer.spec.impersonationProxy to change how the concierge
|
||||
#! handles impersonation.
|
||||
#@schema/title "Impersonation proxy spec"
|
||||
#@schema/desc "Customize CredentialIssuer.spec.impersonationProxy to change how the concierge handles impersonation."
|
||||
impersonation_proxy_spec:
|
||||
#! options are "auto", "disabled" or "enabled".
|
||||
#! If auto, the impersonation proxy will run only if the cluster signing key is not available
|
||||
#! and the other strategy does not work.
|
||||
#! If disabled, the impersonation proxy will never run, which could mean that the concierge
|
||||
#! doesn't work at all.
|
||||
#! If enabled, the impersonation proxy will always run regardless of other strategies available.
|
||||
mode: auto
|
||||
#! The endpoint which the client should use to connect to the impersonation proxy.
|
||||
#! If left unset, the client will default to connecting based on the ClusterIP or LoadBalancer
|
||||
#! endpoint.
|
||||
external_endpoint:
|
||||
service:
|
||||
#! Options are "LoadBalancer", "ClusterIP" and "None".
|
||||
#! LoadBalancer automatically provisions a Service of type LoadBalancer pointing at
|
||||
#! the impersonation proxy. Some cloud providers will allocate
|
||||
#! a public IP address by default even on private clusters.
|
||||
#! ClusterIP automatically provisions a Service of type ClusterIP pointing at the
|
||||
#! impersonation proxy.
|
||||
#! None does not provision either and assumes that you have set the external_endpoint
|
||||
#! and set up your own ingress to connect to the impersonation proxy.
|
||||
type: LoadBalancer
|
||||
#! The annotations that should be set on the ClusterIP or LoadBalancer Service.
|
||||
annotations:
|
||||
{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
|
||||
#@schema/title "Mode"
|
||||
#@ impersonation_mode_desc = "Enables or disables the impersonation proxy. Options are 'auto', 'disabled' or 'enabled'. \
|
||||
#@ If auto, the impersonation proxy will run only if the cluster signing key is \
|
||||
#@ not available and the other strategy does not work. \
|
||||
#@ If enabled, the impersonation proxy will always run regardless of other strategies available. \
|
||||
#@ If disabled, the impersonation proxy will never run, which could mean \
|
||||
#@ that the concierge doesn't work at all."
|
||||
#@schema/desc impersonation_mode_desc
|
||||
#@schema/validation one_of=["auto", "disabled", "enabled"]
|
||||
mode: auto
|
||||
|
||||
#@schema/title "External endpoint"
|
||||
#@ external_endpoint_desc = "The endpoint which the client should use to connect to the impersonation proxy. \
|
||||
#@ If left unset, the client will default to connecting based on the ClusterIP or LoadBalancer endpoint."
|
||||
#@schema/desc external_endpoint_desc
|
||||
#@schema/examples ("Specified impersonation proxy endpoint", "https://1.2.3.4:5678")
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1
|
||||
external_endpoint: ""
|
||||
|
||||
#@schema/title "Service"
|
||||
#@schema/desc "The impersonation proxy service configuration"
|
||||
service:
|
||||
|
||||
#@schema/title "Type"
|
||||
#@ impersonation_service_type_desc = "Service backing the impersonation proxy. Options are 'LoadBalancer', 'ClusterIP' \
|
||||
#@ and 'None'. LoadBalancer automatically provisions a Service of type LoadBalancer pointing at the impersonation \
|
||||
#@ proxy. Some cloud providers will allocate a public IP address by default even on private clusters. ClusterIP \
|
||||
#@ automatically provisions a Service of type ClusterIP pointing at the impersonation proxy. None does not provision \
|
||||
#@ either and assumes that you have set the external_endpoint and set up your own ingress to connect to the impersonation proxy."
|
||||
#@schema/desc impersonation_service_type_desc
|
||||
#@schema/validation one_of=["LoadBalancer", "ClusterIP", "None"]
|
||||
type: LoadBalancer
|
||||
|
||||
#@schema/title "Annotations"
|
||||
#@ annotations_desc = "The annotations that should be set on the ClusterIP or LoadBalancer Service. The default includes \
|
||||
#@ a value for the AWS-specific service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout annotation, which will \
|
||||
#@ be ignored except when using AWS to provide load balancer Services."
|
||||
#@schema/desc annotations_desc
|
||||
#@schema/nullable
|
||||
#@schema/type any=True
|
||||
#@schema/validation ("a map of string keys and string values", validate_strings_map)
|
||||
annotations:
|
||||
|
||||
#@schema/title "Load balancer IP"
|
||||
#@schema/desc "When mode LoadBalancer is set, this will set the LoadBalancer Service's spec.loadBalancerIP."
|
||||
#@schema/examples ("Specifying an IP", "1.2.3.4")
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1
|
||||
load_balancer_ip: ""
|
||||
|
||||
#@schema/title "HTTPS proxy"
|
||||
#@ https_proxy_desc = "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 and JWKS keys for JWTAuthenticators and POSTs to webhooks for WebhookAuthenticators. \
|
||||
#@ The Concierge never makes insecure HTTP calls, so there is no reason to set HTTP_PROXY."
|
||||
#@schema/desc https_proxy_desc
|
||||
#@schema/examples ("Providing a proxy endpoint","http://proxy.example.com")
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1
|
||||
https_proxy: ""
|
||||
|
||||
#@schema/title "No proxy"
|
||||
#@ no_proxy_desc = "Endpoints that should not be proxied. Defaults to not proxying internal Kubernetes endpoints, \
|
||||
#@ localhost endpoints, and the known instance metadata IP address for public cloud providers."
|
||||
#@schema/desc no_proxy_desc
|
||||
no_proxy: "$(KUBERNETES_SERVICE_HOST),169.254.169.254,127.0.0.1,localhost,.svc,.cluster.local"
|
||||
|
||||
#@schema/title "Allowed Ciphers for TLS 1.2"
|
||||
#@ allowed_ciphers_for_tls_onedottwo_desc = "When specified, only the ciphers listed will be used for TLS 1.2. \
|
||||
#@ This includes both server-side and client-side TLS connections. \
|
||||
#@ This list must only include cipher suites that Pinniped is configured to accept \
|
||||
#@ (see internal/crypto/ptls/profiles.go and internal/crypto/ptls/profiles_fips_strict.go). \
|
||||
#@ Allowing too few ciphers may cause critical parts of Pinniped to be unable to function. For example, \
|
||||
#@ Kubernetes pod readiness checks, Pinniped pods acting as a client to the Kubernetes API server, \
|
||||
#@ Pinniped pods acting as a client to external identity providers, or Pinniped pods acting as an APIService server \
|
||||
#@ all need to be able to function with the allowed TLS cipher suites. \
|
||||
#@ An empty array means accept Pinniped's defaults."
|
||||
#@schema/desc allowed_ciphers_for_tls_onedottwo_desc
|
||||
#@schema/examples ("Example with a few secure ciphers", ["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"])
|
||||
#! No type, default, or validation is required here.
|
||||
#! An empty array is perfectly valid, as is any array of strings.
|
||||
allowed_ciphers_for_tls_onedottwo:
|
||||
- ""
|
||||
|
||||
#@schema/title "Audit logging configuration"
|
||||
#@schema/desc "Customize the content of audit log events."
|
||||
audit:
|
||||
|
||||
#@schema/title "Log usernames and groups"
|
||||
#@ log_usernames_and_groups_desc = "Enables or disables printing usernames and group names in audit logs. Options are 'enabled' or 'disabled'. \
|
||||
#@ If enabled, usernames are group names may be printed in audit log events. \
|
||||
#@ If disabled, usernames and group names will be redacted from audit logs because they might contain personally identifiable information."
|
||||
#@schema/desc log_usernames_and_groups_desc
|
||||
#@schema/validation one_of=["enabled", "disabled"]
|
||||
log_usernames_and_groups: disabled
|
||||
|
||||
@@ -20,7 +20,7 @@ kubectl apply -f https://get.pinniped.dev/latest/install-local-user-authenticato
|
||||
|
||||
## Installing a Specific Version with Default Options
|
||||
|
||||
Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number
|
||||
Choose your preferred [release](https://github.com/vmware/pinniped/releases) version number
|
||||
and use it to replace the version number in the URL below.
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
@@ -76,6 +76,15 @@ spec:
|
||||
#! `--validate=false` flag. Note that installing via `kapp` does not complain about this validation error.
|
||||
seccompProfile:
|
||||
type: "RuntimeDefault"
|
||||
tolerations:
|
||||
- key: kubernetes.io/arch
|
||||
effect: NoSchedule
|
||||
operator: Equal
|
||||
value: amd64 #! Allow running on amd64 nodes.
|
||||
- key: kubernetes.io/arch
|
||||
effect: NoSchedule
|
||||
operator: Equal
|
||||
value: arm64 #! Also allow running on arm64 nodes.
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
||||
@@ -1,19 +1,44 @@
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2025 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@data/values
|
||||
#@data/values-schema
|
||||
---
|
||||
#@schema/title "Image repo"
|
||||
#@schema/desc "The repository for the local-user-authenticator container image."
|
||||
#@schema/validation min_len=1
|
||||
image_repo: ghcr.io/vmware/pinniped/pinniped-server
|
||||
|
||||
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
|
||||
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
|
||||
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
|
||||
#@schema/title "Image digest"
|
||||
#@schema/desc "The image digest for the local-user-authenticator container image. If both image_digest or an image_tag are given, only image_digest will be used."
|
||||
#@schema/examples ("Providing a digest", "sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8")
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1, when=lambda _, ctx: ctx.parent["image_tag"] == None
|
||||
image_digest: ""
|
||||
|
||||
#@schema/title "Image tag"
|
||||
#@schema/desc "The image tag for the local-user-authenticator container image. If both image_digest or an image_tag are given, only image_digest will be used."
|
||||
#@schema/examples ("Providing a tag", "v0.25.0")
|
||||
#@schema/validation min_len=1, when=lambda _, ctx: ctx.parent["image_digest"] == None
|
||||
image_tag: latest
|
||||
|
||||
#! Specifies a secret to be used when pulling the above `image_repo` container image.
|
||||
#! Can be used when the above image_repo is a private registry.
|
||||
#! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]'
|
||||
#! Optional.
|
||||
image_pull_dockerconfigjson: #! e.g. {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}
|
||||
#@schema/title "Image pull dockerconfigjson"
|
||||
#@ image_pull_dockerconfigjson_desc = "A base64 encoded secret to be used when pulling the `image_repo` container image. \
|
||||
#@ Can be used when the image_repo is a private registry. Typically, the value would be the output of: \
|
||||
#@ kubectl create secret docker-registry x --docker-server=https://example.io --docker-username='USERNAME' --docker-password='PASSWORD' --dry-run=client -o json | jq -r '.data[\".dockerconfigjson\"]'"
|
||||
#@schema/desc image_pull_dockerconfigjson_desc
|
||||
#@ example_desc = 'base64 encoding of: {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}'
|
||||
#@ example_value = "eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUuaW8iOnsidXNlcm5hbWUiOiJVU0VSTkFNRSIsInBhc3N3b3JkIjoiUEFTU1dPUkQiLCJhdXRoIjoiVlZORlVrNUJUVVU2VUVGVFUxZFBVa1E9In19fQ=="
|
||||
#@schema/examples (example_desc, example_value)
|
||||
#@schema/nullable
|
||||
#@schema/validation min_len=1
|
||||
image_pull_dockerconfigjson: ""
|
||||
|
||||
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
|
||||
#@schema/title "Run as user"
|
||||
#@schema/desc "The user ID that will own the process."
|
||||
#! See the Dockerfile for the reasoning behind this default value.
|
||||
run_as_user: 65532
|
||||
|
||||
#@schema/title "Run as group"
|
||||
#@schema/desc "The group ID that will own the process."
|
||||
#! See the Dockerfile for the reasoning behind this default value.
|
||||
run_as_group: 65532
|
||||
|
||||
@@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.8.0
|
||||
creationTimestamp: null
|
||||
controller-gen.kubebuilder.io/version: v0.20.0
|
||||
name: federationdomains.config.supervisor.pinniped.dev
|
||||
spec:
|
||||
group: config.supervisor.pinniped.dev
|
||||
@@ -21,7 +20,7 @@ spec:
|
||||
- jsonPath: .spec.issuer
|
||||
name: Issuer
|
||||
type: string
|
||||
- jsonPath: .status.status
|
||||
- jsonPath: .status.phase
|
||||
name: Status
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
@@ -33,56 +32,289 @@ spec:
|
||||
description: FederationDomain describes the configuration of an OIDC 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'
|
||||
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'
|
||||
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 of the OIDC provider.
|
||||
properties:
|
||||
identityProviders:
|
||||
description: |-
|
||||
IdentityProviders is the list of identity providers available for use by this FederationDomain.
|
||||
|
||||
An identity provider CR (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server,
|
||||
how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to
|
||||
extract a normalized user identity. Normalized user identities include a username and a list of group names.
|
||||
In contrast, IdentityProviders describes how to use that normalized identity in those Kubernetes clusters which
|
||||
belong to this FederationDomain. Each entry in IdentityProviders can be configured with arbitrary transformations
|
||||
on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid
|
||||
accidental conflicts when multiple identity providers have different users with the same username (e.g.
|
||||
"idp1:ryan" versus "idp2:ryan"). Each entry in IdentityProviders can also implement arbitrary authentication
|
||||
rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow
|
||||
the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could
|
||||
disallow the authentication unless the user belongs to a specific group in the identity provider.
|
||||
|
||||
For backwards compatibility with versions of Pinniped which predate support for multiple identity providers,
|
||||
an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which
|
||||
exist in the same namespace, but also to reject all authentication requests when there is more than one identity
|
||||
provider currently defined. In this backwards compatibility mode, the name of the identity provider resource
|
||||
(e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this
|
||||
FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of
|
||||
relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead
|
||||
explicitly list the identity provider using this IdentityProviders field.
|
||||
items:
|
||||
description: FederationDomainIdentityProvider describes how an identity
|
||||
provider is made available in this FederationDomain.
|
||||
properties:
|
||||
displayName:
|
||||
description: |-
|
||||
DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the
|
||||
kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a
|
||||
disruptive change for those users.
|
||||
minLength: 1
|
||||
type: string
|
||||
objectRef:
|
||||
description: |-
|
||||
ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required.
|
||||
If the reference cannot be resolved then the identity provider will not be made available.
|
||||
Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider,
|
||||
LDAPIdentityProvider, ActiveDirectoryIdentityProvider.
|
||||
properties:
|
||||
apiGroup:
|
||||
description: |-
|
||||
APIGroup is the group for the resource being referenced.
|
||||
If APIGroup is not specified, the specified Kind must be in the core API group.
|
||||
For any other third-party types, APIGroup is required.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind is the type of resource being referenced
|
||||
type: string
|
||||
name:
|
||||
description: Name is the name of resource being referenced
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
transforms:
|
||||
description: |-
|
||||
Transforms is an optional way to specify transformations to be applied during user authentication and
|
||||
session refresh.
|
||||
properties:
|
||||
constants:
|
||||
description: Constants defines constant variables and their
|
||||
values which will be made available to the transform expressions.
|
||||
items:
|
||||
description: |-
|
||||
FederationDomainTransformsConstant defines a constant variable and its value which will be made available to
|
||||
the transform expressions. This is a union type, and Type is the discriminator field.
|
||||
properties:
|
||||
name:
|
||||
description: Name determines the name of the constant.
|
||||
It must be a valid identifier name.
|
||||
maxLength: 64
|
||||
minLength: 1
|
||||
pattern: ^[a-zA-Z][_a-zA-Z0-9]*$
|
||||
type: string
|
||||
stringListValue:
|
||||
description: StringListValue should hold the value
|
||||
when Type is "stringList", and is otherwise ignored.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
stringValue:
|
||||
description: StringValue should hold the value when
|
||||
Type is "string", and is otherwise ignored.
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
Type determines the type of the constant, and indicates which other field should be non-empty.
|
||||
Allowed values are "string" or "stringList".
|
||||
enum:
|
||||
- string
|
||||
- stringList
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
examples:
|
||||
description: |-
|
||||
Examples can optionally be used to ensure that the sequence of transformation expressions are working as
|
||||
expected. Examples define sample input identities which are then run through the expression list, and the
|
||||
results are compared to the expected results. If any example in this list fails, then this
|
||||
identity provider will not be available for use within this FederationDomain, and the error(s) will be
|
||||
added to the FederationDomain status. This can be used to help guard against programming mistakes in the
|
||||
expressions, and also act as living documentation for other administrators to better understand the expressions.
|
||||
items:
|
||||
description: FederationDomainTransformsExample defines
|
||||
a transform example.
|
||||
properties:
|
||||
expects:
|
||||
description: |-
|
||||
Expects is the expected output of the entire sequence of transforms when they are run against the
|
||||
input Username and Groups.
|
||||
properties:
|
||||
groups:
|
||||
description: Groups is the expected list of group
|
||||
names after the transformations have been applied.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
message:
|
||||
description: |-
|
||||
Message is the expected error message of the transforms. When Rejected is true, then Message is the expected
|
||||
message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank,
|
||||
then Message will be treated as the default error message for authentication attempts which are rejected by a
|
||||
policy. When Rejected is false, then Message is the expected error message for some other non-policy
|
||||
transformation error, such as a runtime error. When Rejected is false, there is no default expected Message.
|
||||
type: string
|
||||
rejected:
|
||||
description: |-
|
||||
Rejected is a boolean that indicates whether authentication is expected to be rejected by a policy expression
|
||||
after the transformations have been applied. True means that it is expected that the authentication would be
|
||||
rejected. The default value of false means that it is expected that the authentication would not be rejected
|
||||
by any policy expression.
|
||||
type: boolean
|
||||
username:
|
||||
description: Username is the expected username
|
||||
after the transformations have been applied.
|
||||
type: string
|
||||
type: object
|
||||
groups:
|
||||
description: Groups is the input list of group names.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
username:
|
||||
description: Username is the input username.
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- expects
|
||||
- username
|
||||
type: object
|
||||
type: array
|
||||
expressions:
|
||||
description: |-
|
||||
Expressions are an optional list of transforms and policies to be executed in the order given during every
|
||||
authentication attempt, including during every session refresh.
|
||||
Each is a CEL expression. It may use the basic CEL language as defined in
|
||||
https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in
|
||||
https://github.com/google/cel-go/tree/master/ext#strings.
|
||||
|
||||
The username and groups extracted from the identity provider, and the constants defined in this CR, are
|
||||
available as variables in all expressions. The username is provided via a variable called `username` and
|
||||
the list of group names is provided via a variable called `groups` (which may be an empty list).
|
||||
Each user-provided constants is provided via a variable named `strConst.varName` for string constants
|
||||
and `strListConst.varName` for string list constants.
|
||||
|
||||
The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1.
|
||||
Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated
|
||||
and the authentication attempt is rejected.
|
||||
Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the
|
||||
username or group names.
|
||||
Each username/v1 transform must return the new username (a string), which can be the same as the old username.
|
||||
Transformations of type username/v1 do not return group names, and therefore cannot change the group names.
|
||||
Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old
|
||||
groups list.
|
||||
Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames.
|
||||
After each expression, the new (potentially changed) username or groups get passed to the following expression.
|
||||
|
||||
Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain.
|
||||
During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the
|
||||
authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username
|
||||
and group names have been decided for that authentication attempt.
|
||||
items:
|
||||
description: FederationDomainTransformsExpression defines
|
||||
a transform expression.
|
||||
properties:
|
||||
expression:
|
||||
description: Expression is a CEL expression that will
|
||||
be evaluated based on the Type during an authentication.
|
||||
minLength: 1
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects
|
||||
an authentication attempt. When empty, a default message will be used.
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
Type determines the type of the expression. It must be one of the supported types.
|
||||
Allowed values are "policy/v1", "username/v1", or "groups/v1".
|
||||
enum:
|
||||
- policy/v1
|
||||
- username/v1
|
||||
- groups/v1
|
||||
type: string
|
||||
required:
|
||||
- expression
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
required:
|
||||
- displayName
|
||||
- objectRef
|
||||
type: object
|
||||
type: array
|
||||
issuer:
|
||||
description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery
|
||||
Metadata document, as well as the identifier that it will use for
|
||||
the iss claim in issued JWTs. This field will also be used as the
|
||||
base URL for any endpoints used by the OIDC Provider (e.g., if your
|
||||
issuer is https://example.com/foo, then your authorization endpoint
|
||||
will look like https://example.com/foo/some/path/to/auth/endpoint).
|
||||
\n See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3
|
||||
for more information."
|
||||
description: |-
|
||||
Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the
|
||||
identifier that it will use for the iss claim in issued JWTs. This field will also be used as
|
||||
the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is
|
||||
https://example.com/foo, then your authorization endpoint will look like
|
||||
https://example.com/foo/some/path/to/auth/endpoint).
|
||||
|
||||
See
|
||||
https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
||||
minLength: 1
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: issuer must be an HTTPS URL
|
||||
rule: isURL(self) && url(self).getScheme() == 'https'
|
||||
tls:
|
||||
description: TLS configures how this FederationDomain is served over
|
||||
Transport Layer Security (TLS).
|
||||
description: TLS specifies a secret which will contain Transport Layer
|
||||
Security (TLS) configuration for the FederationDomain.
|
||||
properties:
|
||||
secretName:
|
||||
description: "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. \n Server Name
|
||||
Indication (SNI) is an extension to the Transport Layer Security
|
||||
(TLS) supported by all major browsers. \n 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. \n
|
||||
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.
|
||||
\n When your Issuer URL's host is an IP address, then this field
|
||||
is ignored. SNI does not work for IP addresses."
|
||||
description: |-
|
||||
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 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.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
@@ -91,69 +323,145 @@ spec:
|
||||
status:
|
||||
description: Status of the OIDC provider.
|
||||
properties:
|
||||
lastUpdateTime:
|
||||
description: LastUpdateTime holds the time at which the Status was
|
||||
last updated. It is a pointer to get around some undesirable behavior
|
||||
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: Message provides human-readable details about the Status.
|
||||
conditions:
|
||||
description: Conditions represent the observations of an FederationDomain's
|
||||
current state.
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
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.
|
||||
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 FederationDomain.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
secrets:
|
||||
description: Secrets contains information about this OIDC Provider's
|
||||
secrets.
|
||||
properties:
|
||||
jwks:
|
||||
description: JWKS holds the name of the corev1.Secret in which
|
||||
this OIDC Provider's signing/verification keys are stored. If
|
||||
it is empty, then the signing/verification keys are either unknown
|
||||
or they don't exist.
|
||||
description: |-
|
||||
JWKS holds the name of the corev1.Secret in which this OIDC Provider's signing/verification keys are
|
||||
stored. If it is empty, then the signing/verification keys are either unknown or they don't
|
||||
exist.
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
stateEncryptionKey:
|
||||
description: StateSigningKey holds the name of the corev1.Secret
|
||||
in which this OIDC Provider's key for encrypting state parameters
|
||||
is stored.
|
||||
description: |-
|
||||
StateSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for
|
||||
encrypting state parameters is stored.
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
stateSigningKey:
|
||||
description: StateSigningKey holds the name of the corev1.Secret
|
||||
in which this OIDC Provider's key for signing state parameters
|
||||
is stored.
|
||||
description: |-
|
||||
StateSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for
|
||||
signing state parameters is stored.
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
tokenSigningKey:
|
||||
description: TokenSigningKey holds the name of the corev1.Secret
|
||||
in which this OIDC Provider's key for signing tokens is stored.
|
||||
description: |-
|
||||
TokenSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for
|
||||
signing tokens is stored.
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
status:
|
||||
description: Status holds an enum that describes the state of this
|
||||
OIDC Provider. Note that this Status can represent success or failure.
|
||||
enum:
|
||||
- Success
|
||||
- Duplicate
|
||||
- Invalid
|
||||
- SameIssuerHostMustUseSameSecret
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
@@ -162,9 +470,3 @@ spec:
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user