mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-30 01:22:30 +00:00
Compare commits
1076 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d6a2af894 | ||
|
|
dab653f8df | ||
|
|
8698d71809 | ||
|
|
96d4d3ec7c | ||
|
|
7c87d7447c | ||
|
|
a2e578bdbb | ||
|
|
366782ab75 | ||
|
|
e4e764860a | ||
|
|
bb71545dee | ||
|
|
19ec85c84e | ||
|
|
427eef2038 | ||
|
|
28169637c8 | ||
|
|
a5b83c90a6 | ||
|
|
454b792afb | ||
|
|
cb4085bfd9 | ||
|
|
9b0dc92025 | ||
|
|
7859a7b5c2 | ||
|
|
bdcf468e52 | ||
|
|
efaca05999 | ||
|
|
316e6171d4 | ||
|
|
04544b3d3c | ||
|
|
85102b0118 | ||
|
|
55de160551 | ||
|
|
cec9f3c4d7 | ||
|
|
16f562e81c | ||
|
|
92ccc0ec84 | ||
|
|
74175f2518 | ||
|
|
0a1ee9e37c | ||
|
|
05f5bac405 | ||
|
|
0195894a50 | ||
|
|
27c1d2144a | ||
|
|
88aba645b8 | ||
|
|
402c213183 | ||
|
|
17acc7caa6 | ||
|
|
6b7a230ca5 | ||
|
|
c7a8c429ed | ||
|
|
f0a1555aca | ||
|
|
ccd338fa50 | ||
|
|
4e7214c6b5 | ||
|
|
2297ee4b81 | ||
|
|
85daec4748 | ||
|
|
cf014656af | ||
|
|
b3b3c2303f | ||
|
|
0ff66c718b | ||
|
|
1bb8a43e04 | ||
|
|
655bbce42a | ||
|
|
9258745ec7 | ||
|
|
fcffab9a4c | ||
|
|
92f7f12bab | ||
|
|
7c40185676 | ||
|
|
abf19f649d | ||
|
|
0a2a716796 | ||
|
|
a27e398923 | ||
|
|
b19af2e135 | ||
|
|
883007aa1b | ||
|
|
ba1470ea9d | ||
|
|
0d285ce993 | ||
|
|
e43bd59688 | ||
|
|
0c8d885c26 | ||
|
|
23fb84029b | ||
|
|
d2dfe3634a | ||
|
|
5489f68e2f | ||
|
|
4eb500cc41 | ||
|
|
871a9fb0c6 | ||
|
|
4cb0152ea1 | ||
|
|
42af8acd1e | ||
|
|
df014dadc3 | ||
|
|
bb657e7432 | ||
|
|
d580695faa | ||
|
|
ba80b691e1 | ||
|
|
41c017c9da | ||
|
|
5078cdbc90 | ||
|
|
e5718351ba | ||
|
|
36ff0d52da | ||
|
|
ad3086b8f1 | ||
|
|
6c29f347b4 | ||
|
|
a86949d0be | ||
|
|
44f03af4b9 | ||
|
|
ce5cfde11e | ||
|
|
40d70bf1fc | ||
|
|
19100d68ef | ||
|
|
1d44aa945d | ||
|
|
68547f767d | ||
|
|
44e5e9d8c9 | ||
|
|
43694777d5 | ||
|
|
f579b1cb9f | ||
|
|
2d32e0fa7d | ||
|
|
6f221678df | ||
|
|
e24040b0a9 | ||
|
|
1d269d2f6d | ||
|
|
d4a7f0b3e1 | ||
|
|
d22099ac33 | ||
|
|
e2cf9f6b74 | ||
|
|
74daa1da64 | ||
|
|
475da05185 | ||
|
|
86bfd4f5e4 | ||
|
|
d453bf3403 | ||
|
|
2b9b034bd2 | ||
|
|
d20cab10b9 | ||
|
|
399737e7c6 | ||
|
|
1c5a2b8892 | ||
|
|
c17e7bec49 | ||
|
|
c71ffdcd1e | ||
|
|
c590c8ff41 | ||
|
|
c0617ceda4 | ||
|
|
f7751d13fe | ||
|
|
3077034b2d | ||
|
|
89cef2ea6c | ||
|
|
211f4b23d1 | ||
|
|
132ec0d2ad | ||
|
|
ae505d8009 | ||
|
|
c356710f1f | ||
|
|
b9d186e8a3 | ||
|
|
03a8160a91 | ||
|
|
f379eee7a3 | ||
|
|
4f5312807b | ||
|
|
cec3c2133a | ||
|
|
05afae60c2 | ||
|
|
6239a567a8 | ||
|
|
e4d418a076 | ||
|
|
c4727d57c8 | ||
|
|
b4a39ba3c4 | ||
|
|
cf627a82cb | ||
|
|
42d31a7085 | ||
|
|
02b8ed7e0b | ||
|
|
5e9087263d | ||
|
|
a20aee5f18 | ||
|
|
1d18908055 | ||
|
|
1c5da35527 | ||
|
|
61c21d2977 | ||
|
|
90e6298e29 | ||
|
|
04b8f0b455 | ||
|
|
8657b0e3e7 | ||
|
|
0089540b07 | ||
|
|
26c47d564f | ||
|
|
62c6d53a21 | ||
|
|
96474b3d99 | ||
|
|
964d16110e | ||
|
|
d57637ee56 | ||
|
|
8ce4bb6dc1 | ||
|
|
a7c88b599c | ||
|
|
e0901f4fe5 | ||
|
|
cf25c308cd | ||
|
|
9d11be899c | ||
|
|
66ddcf98d3 | ||
|
|
3fb683f64e | ||
|
|
52409f86e8 | ||
|
|
91c8a3ebed | ||
|
|
52cb0bbc07 | ||
|
|
eb2a68fec0 | ||
|
|
e05a46b7f5 | ||
|
|
46304c8137 | ||
|
|
7a812ac5ed | ||
|
|
71d6281e39 | ||
|
|
bb30569e41 | ||
|
|
942c55cf51 | ||
|
|
50085a505b | ||
|
|
69964fc788 | ||
|
|
5b96d014b4 | ||
|
|
84c3c3aa9c | ||
|
|
592563124b | ||
|
|
c4d7e5c124 | ||
|
|
34fd0ea2e2 | ||
|
|
5678fc6196 | ||
|
|
4a17e1e736 | ||
|
|
724acdca1d | ||
|
|
a027f1ae2c | ||
|
|
d2891554a4 | ||
|
|
25b4d82d87 | ||
|
|
01ddc7ac36 | ||
|
|
58bbffded4 | ||
|
|
a464c81711 | ||
|
|
f18cbcd9a6 | ||
|
|
ac7d65c4a8 | ||
|
|
65fa47cbcd | ||
|
|
a06b38d2cd | ||
|
|
7773fb8afe | ||
|
|
f4badb3961 | ||
|
|
1e32530d7b | ||
|
|
0ab8e14e4a | ||
|
|
f1109afa79 | ||
|
|
22be97eeda | ||
|
|
d23f3c9428 | ||
|
|
c3e037b24e | ||
|
|
62afb34877 | ||
|
|
fd5ed2e5da | ||
|
|
ca82609d1a | ||
|
|
a6dc5b912f | ||
|
|
d73093a694 | ||
|
|
85560299e0 | ||
|
|
b42b1c1110 | ||
|
|
84733405d0 | ||
|
|
48c8fabb5c | ||
|
|
1f51159d22 | ||
|
|
5f679059d5 | ||
|
|
8afbb4eb4f | ||
|
|
c9b4598fa0 | ||
|
|
ef33846d7d | ||
|
|
8b4ed86071 | ||
|
|
8b74dd824b | ||
|
|
727035a2dc | ||
|
|
fc82fde585 | ||
|
|
f352db8072 | ||
|
|
f075d95183 | ||
|
|
22a66c1192 | ||
|
|
474266f918 | ||
|
|
8e8af51955 | ||
|
|
d5759c9951 | ||
|
|
bf99348faf | ||
|
|
2789af79f6 | ||
|
|
71cae75758 | ||
|
|
90db3ad51b | ||
|
|
32c9aa5087 | ||
|
|
bbaa820278 | ||
|
|
287a5d225a | ||
|
|
f17f7c0c6a | ||
|
|
54c5bcc9a1 | ||
|
|
58ab57201f | ||
|
|
f4829178b3 | ||
|
|
295f013580 | ||
|
|
53b58f65b2 | ||
|
|
cc3875f048 | ||
|
|
d8e1521457 | ||
|
|
e150111b27 | ||
|
|
9e27c28b39 | ||
|
|
5d23068690 | ||
|
|
1050f39789 | ||
|
|
00978c15f7 | ||
|
|
8ea1bd3dfb | ||
|
|
91085e68f9 | ||
|
|
f99f7be836 | ||
|
|
890d9c3216 | ||
|
|
cb0ee07b51 | ||
|
|
8e1d70562d | ||
|
|
5d8d7246c2 | ||
|
|
3b4f521596 | ||
|
|
e5c8cbb3a4 | ||
|
|
7696f4256d | ||
|
|
aaa4861373 | ||
|
|
b3d0b28bd0 | ||
|
|
5c283d941c | ||
|
|
94e90a5d26 | ||
|
|
be6f9f83ce | ||
|
|
3b8edb84a5 | ||
|
|
8fb35c6569 | ||
|
|
3899292e89 | ||
|
|
b06de69f6a | ||
|
|
ac4bc02817 | ||
|
|
708164b878 | ||
|
|
e30cf6e51a | ||
|
|
ee30b78117 | ||
|
|
64aba7e703 | ||
|
|
c6c3a80a86 | ||
|
|
a7af63ca3a | ||
|
|
ae72d30cec | ||
|
|
fec59eb1bf | ||
|
|
f6273b0604 | ||
|
|
deb699a84a | ||
|
|
cac45fd999 | ||
|
|
0bdd1bc68f | ||
|
|
4605846499 | ||
|
|
4670890a82 | ||
|
|
d204b46c18 | ||
|
|
b3208f0ca6 | ||
|
|
be7bf9c193 | ||
|
|
2bba39d723 | ||
|
|
dc567d0d1f | ||
|
|
143837c136 | ||
|
|
11eb18d348 | ||
|
|
d5cf5b91d6 | ||
|
|
48b58e2fad | ||
|
|
7ef3d42e01 | ||
|
|
33461ddc14 | ||
|
|
238c9e6743 | ||
|
|
25cda4f3e6 | ||
|
|
c71703e4db | ||
|
|
5527566a36 | ||
|
|
b6580b303a | ||
|
|
405a27ba90 | ||
|
|
43f66032a9 | ||
|
|
91a1fec5cf | ||
|
|
d0b37a7c90 | ||
|
|
5029495fdb | ||
|
|
ac6ff1a03c | ||
|
|
95ee9f0b00 | ||
|
|
9fba8d2203 | ||
|
|
428f389c7d | ||
|
|
71d4e05fb6 | ||
|
|
1904f8ddc3 | ||
|
|
6b801056b5 | ||
|
|
674cd4a88c | ||
|
|
7217cf4892 | ||
|
|
40c931bdc5 | ||
|
|
2823d4d1e3 | ||
|
|
6d83ecb420 | ||
|
|
c27eb17f23 | ||
|
|
58363bca2c | ||
|
|
3bf39797bb | ||
|
|
3a840cee76 | ||
|
|
04e9897d51 | ||
|
|
ff9095f9c4 | ||
|
|
2e18c88e33 | ||
|
|
9f91c6c884 | ||
|
|
59fd1997f4 | ||
|
|
74f3ce5dcd | ||
|
|
d403c8b44b | ||
|
|
e130da6daa | ||
|
|
2f7dbed321 | ||
|
|
709c10227f | ||
|
|
f0d120a6ca | ||
|
|
1f5480cd5c | ||
|
|
f1e63c55d4 | ||
|
|
562951b77a | ||
|
|
dbd2cb4563 | ||
|
|
1c746feafe | ||
|
|
49683975ab | ||
|
|
4be26fc1a6 | ||
|
|
f590a3a88b | ||
|
|
e26486bd41 | ||
|
|
5c2e890ecd | ||
|
|
715cf7748a | ||
|
|
e0456b4485 | ||
|
|
629bf61655 | ||
|
|
738e6aa3cc | ||
|
|
76dc39ac2d | ||
|
|
43fee6bb94 | ||
|
|
8026729c43 | ||
|
|
d19d63ad7d | ||
|
|
a6141e911c | ||
|
|
5ff2be973c | ||
|
|
73201ba575 | ||
|
|
125d891cd5 | ||
|
|
682a47f739 | ||
|
|
594e47efdf | ||
|
|
f09a45382e | ||
|
|
d78b845575 | ||
|
|
1929b47dda | ||
|
|
3efa7bdcc2 | ||
|
|
6a9eb87c35 | ||
|
|
3eba3e07c6 | ||
|
|
9f06869f76 | ||
|
|
3f41261580 | ||
|
|
551249fb69 | ||
|
|
1a610022cf | ||
|
|
524ff21b7f | ||
|
|
913c140be8 | ||
|
|
e06c696bea | ||
|
|
269db6b7c2 | ||
|
|
addf632e7c | ||
|
|
87489da316 | ||
|
|
898f2bf942 | ||
|
|
918c50f6a7 | ||
|
|
9ca82116f1 | ||
|
|
564c1f8ae5 | ||
|
|
c88aad873b | ||
|
|
9d27e6b4c6 | ||
|
|
5b327a2b37 | ||
|
|
7114988eec | ||
|
|
3a47060256 | ||
|
|
492f6cfddf | ||
|
|
f417f706b9 | ||
|
|
02335e2ade | ||
|
|
9b9e733a7d | ||
|
|
df78e00df3 | ||
|
|
b5ed4e6a13 | ||
|
|
500b444bad | ||
|
|
d3e2859238 | ||
|
|
5686591420 | ||
|
|
6903196c18 | ||
|
|
af4cd1b515 | ||
|
|
2acfafd5a5 | ||
|
|
a5067cdbb3 | ||
|
|
5aa08756e0 | ||
|
|
0e66b0b165 | ||
|
|
87660611d2 | ||
|
|
9968c0d234 | ||
|
|
193fcb87bb | ||
|
|
a08e4ec043 | ||
|
|
e38a7548cc | ||
|
|
b5dea42bbe | ||
|
|
d06fe15a68 | ||
|
|
e6301f0e74 | ||
|
|
aca33e45fb | ||
|
|
46825b1c9f | ||
|
|
2ee3cec5ed | ||
|
|
75d92079e4 | ||
|
|
0be77c3bf2 | ||
|
|
d4a6a61560 | ||
|
|
abc3df8df9 | ||
|
|
5932bce54d | ||
|
|
41ff3e0917 | ||
|
|
f62c6e806d | ||
|
|
79e3980f1f | ||
|
|
8f2e8b8a6c | ||
|
|
e4fda80fcc | ||
|
|
5263e0bae5 | ||
|
|
b8205006ca | ||
|
|
7ee1f8c441 | ||
|
|
854903c4ed | ||
|
|
cedbe82bbb | ||
|
|
a741041737 | ||
|
|
83f418e7f2 | ||
|
|
e25de9e559 | ||
|
|
87c7e89b13 | ||
|
|
4722422aae | ||
|
|
a39b328778 | ||
|
|
343238fa9b | ||
|
|
a69fe68362 | ||
|
|
01713c7ce1 | ||
|
|
ab750f48aa | ||
|
|
d7d8630e08 | ||
|
|
cd7f5741d8 | ||
|
|
c8dc03b06a | ||
|
|
83001d8cce | ||
|
|
d2251d2ea7 | ||
|
|
f330b52076 | ||
|
|
af2af567be | ||
|
|
ec2956d54e | ||
|
|
35cf1a00c8 | ||
|
|
0d43105759 | ||
|
|
67d5c91713 | ||
|
|
81148866e0 | ||
|
|
349d3dad83 | ||
|
|
049abfb94c | ||
|
|
033e1f0399 | ||
|
|
d2d0dae4ed | ||
|
|
0a47aa4843 | ||
|
|
d780bf64bc | ||
|
|
b57878ebc5 | ||
|
|
1932b03c39 | ||
|
|
be8118ec2e | ||
|
|
1a4687a40a | ||
|
|
b13c494f93 | ||
|
|
e5a61f3b95 | ||
|
|
9621ad9d2c | ||
|
|
f2021f1b53 | ||
|
|
e2fad6932f | ||
|
|
9ee11d2a49 | ||
|
|
bf39f930d4 | ||
|
|
450ce6a4aa | ||
|
|
c970dd1fb0 | ||
|
|
18a2a27a06 | ||
|
|
47582e3290 | ||
|
|
89eff28549 | ||
|
|
d9a3992b3b | ||
|
|
75dd98a965 | ||
|
|
61362f8187 | ||
|
|
f89f2281d8 | ||
|
|
f5b11a0239 | ||
|
|
5e8945c616 | ||
|
|
2331c2dae2 | ||
|
|
2014f4623d | ||
|
|
fabc08b01b | ||
|
|
468463ce1d | ||
|
|
520eb43bfd | ||
|
|
5de9bac4ac | ||
|
|
523a8d432f | ||
|
|
1ab1d41735 | ||
|
|
36168122cc | ||
|
|
150e879a68 | ||
|
|
b16e84d90a | ||
|
|
722aa72206 | ||
|
|
2d0cb16239 | ||
|
|
b4bb0db6e5 | ||
|
|
fd9d9b8c73 | ||
|
|
44f6fd4437 | ||
|
|
f0d5923091 | ||
|
|
85ebaa96d5 | ||
|
|
cf5bc9f1b4 | ||
|
|
0d02ba6af3 | ||
|
|
74a569fa82 | ||
|
|
01c0514057 | ||
|
|
0d42c1e9fe | ||
|
|
4606f1d8bd | ||
|
|
1307c49212 | ||
|
|
b01665386d | ||
|
|
599d70d6dc | ||
|
|
901ddd1870 | ||
|
|
8b549f66d4 | ||
|
|
4780c39640 | ||
|
|
7e76b66639 | ||
|
|
fff90ed2ca | ||
|
|
62651eddb0 | ||
|
|
ec25259901 | ||
|
|
e4dd83887a | ||
|
|
562942cdbf | ||
|
|
025b37f839 | ||
|
|
63c39454f6 | ||
|
|
657488fe90 | ||
|
|
9e61640c92 | ||
|
|
94d6b76958 | ||
|
|
424c112bbc | ||
|
|
3bb95f1de2 | ||
|
|
0b66321902 | ||
|
|
297a484948 | ||
|
|
13372a43e6 | ||
|
|
54e0b83146 | ||
|
|
94c370ac85 | ||
|
|
b5063e59ab | ||
|
|
a6f95cfff1 | ||
|
|
eaea3471ec | ||
|
|
4a785e73e6 | ||
|
|
51f1a0ec13 | ||
|
|
9af3cb1115 | ||
|
|
18ccf11905 | ||
|
|
1a131e64fe | ||
|
|
e885114221 | ||
|
|
26da763962 | ||
|
|
4a456446ff | ||
|
|
efeb25b8eb | ||
|
|
f595e81dbb | ||
|
|
0f5f72829b | ||
|
|
f40fd29c7c | ||
|
|
35479e2978 | ||
|
|
742b70d6a4 | ||
|
|
dab5ff3788 | ||
|
|
99099fd32f | ||
|
|
65cab53a11 | ||
|
|
8c660f09bc | ||
|
|
ac431ddc6d | ||
|
|
3e1e8880f7 | ||
|
|
14b8fcc472 | ||
|
|
20b1c41bf5 | ||
|
|
f5bf8978a3 | ||
|
|
514ee5b883 | ||
|
|
39d7f8b6eb | ||
|
|
609883c49e | ||
|
|
f15fc66e06 | ||
|
|
6479015caf | ||
|
|
67dca688d7 | ||
|
|
b391d5ae02 | ||
|
|
29ca8acab4 | ||
|
|
1ae3c6a1ad | ||
|
|
22092e9aed | ||
|
|
874f938fc7 | ||
|
|
4804c837d4 | ||
|
|
f0652c1ce1 | ||
|
|
044443f315 | ||
|
|
9ca72fcd30 | ||
|
|
3008d1a85c | ||
|
|
6c2a775c9b | ||
|
|
41d3e3b6ec | ||
|
|
20b86ac0a9 | ||
|
|
df0e715bb7 | ||
|
|
6723ed9fd8 | ||
|
|
f98aa96ed3 | ||
|
|
675bbb2aba | ||
|
|
e25eb05450 | ||
|
|
dbde150c38 | ||
|
|
c0fcd27594 | ||
|
|
1ddc85495f | ||
|
|
716659b74a | ||
|
|
696c2b9133 | ||
|
|
0770682bf9 | ||
|
|
88ff3164a2 | ||
|
|
56d316e8d3 | ||
|
|
9fc7f43245 | ||
|
|
47f5e822d0 | ||
|
|
cc99d9aeb4 | ||
|
|
7ece196893 | ||
|
|
a08a28d67b | ||
|
|
2634c9f04a | ||
|
|
29a1ca5168 | ||
|
|
5240f5e84a | ||
|
|
a8bccc5432 | ||
|
|
f167a075dd | ||
|
|
8136c787a7 | ||
|
|
3e13b5f39d | ||
|
|
1a2940c278 | ||
|
|
4bb0fdeddd | ||
|
|
4ce77c4837 | ||
|
|
1586171876 | ||
|
|
165bef7809 | ||
|
|
b80cbb8cc5 | ||
|
|
71e38d232e | ||
|
|
778c194cc4 | ||
|
|
a8754b5658 | ||
|
|
1c66ffd5ff | ||
|
|
ab94b97f4a | ||
|
|
d6a172214d | ||
|
|
638fa7ba27 | ||
|
|
b5ffab6330 | ||
|
|
8556a638a2 | ||
|
|
44c7f8daf0 | ||
|
|
1efa4da80c | ||
|
|
62785674c3 | ||
|
|
9e4f601a3f | ||
|
|
bb7e7fe81e | ||
|
|
10c4cb4493 | ||
|
|
36819989a3 | ||
|
|
bed2d2dd62 | ||
|
|
4bd83add35 | ||
|
|
90b2854032 | ||
|
|
5c62a9d0bd | ||
|
|
96fda6ed13 | ||
|
|
263a33cc85 | ||
|
|
b3b108500a | ||
|
|
67a568811a | ||
|
|
620a4d55b7 | ||
|
|
a52872cd03 | ||
|
|
0dfb3e95c5 | ||
|
|
e532a88647 | ||
|
|
54a8297cc4 | ||
|
|
2843c4f8cb | ||
|
|
9b818dbf10 | ||
|
|
6a350aa4e1 | ||
|
|
cc51c72c12 | ||
|
|
0ab9927115 | ||
|
|
204c8e8dbc | ||
|
|
638d9235a2 | ||
|
|
81a4c84f46 | ||
|
|
9f509d3f13 | ||
|
|
5f3eab2538 | ||
|
|
c45d48d027 | ||
|
|
09560fd8dc | ||
|
|
264778113d | ||
|
|
b5889f37ff | ||
|
|
45e4695444 | ||
|
|
6a21499ed3 | ||
|
|
211d4fd0b6 | ||
|
|
8ffd9fdc4e | ||
|
|
ddc632b99c | ||
|
|
c176d15aa7 | ||
|
|
d76ac56df2 | ||
|
|
d86b24ca2f | ||
|
|
73716f1b91 | ||
|
|
521adffb17 | ||
|
|
c79930f419 | ||
|
|
70d607d87e | ||
|
|
4c2a0b4872 | ||
|
|
e9d5743845 | ||
|
|
83085aa3d6 | ||
|
|
8e438e22e9 | ||
|
|
b9ce84fd68 | ||
|
|
9dfa1f5ee5 | ||
|
|
da7216c1ef | ||
|
|
f63ded99bc | ||
|
|
e6e6497022 | ||
|
|
5c28d36c9b | ||
|
|
e7b7b597ff | ||
|
|
e5da119000 | ||
|
|
12a3636351 | ||
|
|
939b6b12cc | ||
|
|
8d75825635 | ||
|
|
923938ab26 | ||
|
|
352d4dc5b1 | ||
|
|
e0fe184c89 | ||
|
|
a6e1a949d2 | ||
|
|
47b66ceaa7 | ||
|
|
6bba529b10 | ||
|
|
14ff5ee4ff | ||
|
|
51263a0f07 | ||
|
|
fec3d92f26 | ||
|
|
7b8c86b38e | ||
|
|
f0c4305e53 | ||
|
|
e24d5891dd | ||
|
|
25c1f0d523 | ||
|
|
05571abb74 | ||
|
|
05daa9eff5 | ||
|
|
dab7b57da0 | ||
|
|
12d35583c5 | ||
|
|
599c537d24 | ||
|
|
38f3ea3f2f | ||
|
|
e450a348c5 | ||
|
|
11d820be06 | ||
|
|
63816aa3ba | ||
|
|
e5314164c5 | ||
|
|
7781a2e17a | ||
|
|
4ab704b7de | ||
|
|
f6ded84f07 | ||
|
|
abf606ab72 | ||
|
|
b59a4f3fec | ||
|
|
3b461572ea | ||
|
|
271c006b6c | ||
|
|
043cefcd9f | ||
|
|
2296faaeef | ||
|
|
fec24d307e | ||
|
|
64b13043ed | ||
|
|
5501b5aa13 | ||
|
|
064e3144a2 | ||
|
|
1f5978aa1a | ||
|
|
1c55c857f4 | ||
|
|
2b6859b161 | ||
|
|
9968d501f4 | ||
|
|
9450048acf | ||
|
|
702f9965ab | ||
|
|
c53507809d | ||
|
|
9cd2b6e855 | ||
|
|
4e25bcd4b2 | ||
|
|
5add31d263 | ||
|
|
88c4335b4b | ||
|
|
623830bf1f | ||
|
|
30f476e1ac | ||
|
|
7b82b7a010 | ||
|
|
44bf925c3e | ||
|
|
d2a6d7689f | ||
|
|
23dbd7cab6 | ||
|
|
e4321cb369 | ||
|
|
ad66f67dc9 | ||
|
|
55bc3dee7f | ||
|
|
fdbeb213fb | ||
|
|
1817d6c751 | ||
|
|
476cc98e5a | ||
|
|
4cbf4959f2 | ||
|
|
e4e4e686f6 | ||
|
|
d5be37673a | ||
|
|
7bb5657c4d | ||
|
|
fe9f12a29c | ||
|
|
bea75bb7ac | ||
|
|
081de8da62 | ||
|
|
469f864de3 | ||
|
|
dc510792c4 | ||
|
|
8b6fe0ac70 | ||
|
|
d47603472d | ||
|
|
d4baeff94e | ||
|
|
210114dbe1 | ||
|
|
4ebd0f5f12 | ||
|
|
f02b39b80f | ||
|
|
608be8332e | ||
|
|
3742719427 | ||
|
|
f00a02dcca | ||
|
|
017c891fb8 | ||
|
|
d8baa43903 | ||
|
|
5c741bc423 | ||
|
|
e99e175ce2 | ||
|
|
003e3e3c4d | ||
|
|
391202c253 | ||
|
|
95bb4c4be5 | ||
|
|
32422f18f1 | ||
|
|
d52f500b83 | ||
|
|
defad3cdd7 | ||
|
|
c6d7724b67 | ||
|
|
3359311228 | ||
|
|
7e16619146 | ||
|
|
a084544f08 | ||
|
|
c2588cf035 | ||
|
|
2179c2879a | ||
|
|
b6e217e13a | ||
|
|
6f2882b831 | ||
|
|
cd6e48bfa8 | ||
|
|
818535f30d | ||
|
|
c0361645e2 | ||
|
|
6bf8bfe9a8 | ||
|
|
ea130ea781 | ||
|
|
03619fc878 | ||
|
|
454348b2fd | ||
|
|
cda8bd6e26 | ||
|
|
c0d32f10b2 | ||
|
|
ce5b05f912 | ||
|
|
176fb6a139 | ||
|
|
9501168265 | ||
|
|
2e79664f3d | ||
|
|
e70788204b | ||
|
|
f6646eb2b7 | ||
|
|
75cfda0ffe | ||
|
|
bde54ef643 | ||
|
|
d90398815b | ||
|
|
7683a98792 | ||
|
|
d7e9568137 | ||
|
|
904086cbec | ||
|
|
5e95c25d4f | ||
|
|
c9b1982767 | ||
|
|
f69d095a69 | ||
|
|
1e7f2c7735 | ||
|
|
9af75d23fb | ||
|
|
d0df2009ac | ||
|
|
964d4889c4 | ||
|
|
a537287601 | ||
|
|
fdfc854f8c | ||
|
|
331fef8fae | ||
|
|
4470d3d2d1 | ||
|
|
698bffc2ad | ||
|
|
6ff3e42602 | ||
|
|
3e50b4e129 | ||
|
|
d856221f56 | ||
|
|
f519f0cb09 | ||
|
|
c03fe2d1fe | ||
|
|
2749044625 | ||
|
|
f73c70d8f9 | ||
|
|
ebd5e45fa6 | ||
|
|
6154883855 | ||
|
|
ebe01a5aef | ||
|
|
28d00ce67b | ||
|
|
50e4531215 | ||
|
|
1a9922d050 | ||
|
|
f2a48aee2b | ||
|
|
d162cb9adf | ||
|
|
14a28bec24 | ||
|
|
dae62929e0 | ||
|
|
c22ac17dfe | ||
|
|
08c446a3e1 | ||
|
|
bd8c243636 | ||
|
|
e4bf6e068f | ||
|
|
120e46b5f7 | ||
|
|
257d69045d | ||
|
|
05a188d4cd | ||
|
|
205c22ddbe | ||
|
|
aa79bc7609 | ||
|
|
a36914f5ca | ||
|
|
cc8f0b623c | ||
|
|
de6837226e | ||
|
|
3a32833306 | ||
|
|
74df6d138b | ||
|
|
0dd2b358fb | ||
|
|
6520c5a3a1 | ||
|
|
5a43a5d53a | ||
|
|
897340860b | ||
|
|
4d2035ab2a | ||
|
|
d85135c12e | ||
|
|
30a392b900 | ||
|
|
4ab3c64b70 | ||
|
|
2515b2d710 | ||
|
|
10a1e29e15 | ||
|
|
2319606cd2 | ||
|
|
10168ab2e7 | ||
|
|
c5b784465b | ||
|
|
236dbdb2c4 | ||
|
|
6887d0aca2 | ||
|
|
64e0dbb481 | ||
|
|
e47543233c | ||
|
|
2460568be3 | ||
|
|
1b31489347 | ||
|
|
ab6452ace7 | ||
|
|
c46aa1c29d | ||
|
|
939ea30030 | ||
|
|
efd973fa17 | ||
|
|
4f671f5dca | ||
|
|
a5384a6e38 | ||
|
|
e64f2fe7fb | ||
|
|
035362f4d3 | ||
|
|
8065a8d2e6 | ||
|
|
e22ad6171a | ||
|
|
c2b0acf241 | ||
|
|
00694c9cb6 | ||
|
|
dc96f398da | ||
|
|
755a87cdbb | ||
|
|
c538a4e8e8 | ||
|
|
41949d8e07 | ||
|
|
4c162be8bf | ||
|
|
b530cef3b1 | ||
|
|
c82f568b2c | ||
|
|
5e4746e96b | ||
|
|
077aa8a42e | ||
|
|
d509e7012e | ||
|
|
8c0bafd5be | ||
|
|
12b13b1ea5 | ||
|
|
5b1dc0abdf | ||
|
|
253e0f8e9a | ||
|
|
87f2899047 | ||
|
|
6ddf4c04e6 | ||
|
|
1d68841c78 | ||
|
|
f77c92560f | ||
|
|
c12a23725d | ||
|
|
d5beba354b | ||
|
|
71712b2d00 | ||
|
|
ad3f04a982 | ||
|
|
a52455504f | ||
|
|
4f154100ff | ||
|
|
d2d9b1e49e | ||
|
|
c9ce067a0e | ||
|
|
1af25552a0 | ||
|
|
a64786a728 | ||
|
|
2d28d1da19 | ||
|
|
78fdc59d2d | ||
|
|
29d7f406f7 | ||
|
|
3449b896d6 | ||
|
|
22ca2da1ff | ||
|
|
e98c6dfdd8 | ||
|
|
fcd8c585c3 | ||
|
|
a918e9fb97 | ||
|
|
34accc3dee | ||
|
|
61d64fc4c6 | ||
|
|
b793b9a17e | ||
|
|
7b1ecf79a6 | ||
|
|
32b038c639 | ||
|
|
d13bb07b3e | ||
|
|
24396b6af1 | ||
|
|
006dc8aa79 | ||
|
|
2a2e2f532b | ||
|
|
1078bf4dfb | ||
|
|
c14621428f | ||
|
|
6582c23edb | ||
|
|
0b300cbe42 | ||
|
|
876f0a55d8 | ||
|
|
c853707889 | ||
|
|
005133fbfb | ||
|
|
0cb1538b39 | ||
|
|
0abe10e6b2 | ||
|
|
883b90923d | ||
|
|
d6a0dfa497 | ||
|
|
29d5e43220 | ||
|
|
eef1fd0c64 | ||
|
|
b2be83ee45 | ||
|
|
b20a8358d3 | ||
|
|
a58b460bcb | ||
|
|
8fd6a71312 | ||
|
|
6efbd81f75 | ||
|
|
a059d8dfce | ||
|
|
8c0a073cb6 | ||
|
|
389cd3486b | ||
|
|
eac108aee5 | ||
|
|
49ec16038c | ||
|
|
4bd68b1fa1 | ||
|
|
73419313ee | ||
|
|
4750d7d7d2 | ||
|
|
ba0dc3bf52 | ||
|
|
5d8594b285 | ||
|
|
ce1b6303d9 | ||
|
|
36bc679142 | ||
|
|
c4f6fd5b3c | ||
|
|
52f58477b8 | ||
|
|
d848499176 | ||
|
|
c3b7d21037 | ||
|
|
832bc2726e | ||
|
|
3833ba0430 | ||
|
|
ec133b9743 | ||
|
|
d8c6894cbc | ||
|
|
b102aa8991 | ||
|
|
9eb97e2683 | ||
|
|
fea626b654 | ||
|
|
16163b989b | ||
|
|
165fce67af | ||
|
|
6a8f377781 | ||
|
|
d24cf4b8a7 | ||
|
|
34e15f03c3 | ||
|
|
274e6281a8 | ||
|
|
7146cb3880 | ||
|
|
9dfbe60253 | ||
|
|
1734280a19 | ||
|
|
9a0f75980d | ||
|
|
ddd1d29e5d | ||
|
|
03f09c6870 | ||
|
|
f99c186c55 | ||
|
|
14b8def320 | ||
|
|
5697adc36a | ||
|
|
9c1c760f56 | ||
|
|
48f2ae9eb4 | ||
|
|
7c9aff3278 | ||
|
|
58607c7e81 | ||
|
|
1b3103c9b5 | ||
|
|
666c0b0e18 | ||
|
|
f0fc84c922 | ||
|
|
7b7901af36 | ||
|
|
57453773ea | ||
|
|
f4fcb9bde6 | ||
|
|
0799a538dc | ||
|
|
4f700d4811 | ||
|
|
d7edc41c24 | ||
|
|
333a3ab4c2 | ||
|
|
730092f39c | ||
|
|
d3599c541b | ||
|
|
454f35ccd6 | ||
|
|
27daf0a2fe | ||
|
|
8bf03257f4 | ||
|
|
1ad2c38509 | ||
|
|
84cc42b2ca | ||
|
|
4c68050706 | ||
|
|
aa826a1579 | ||
|
|
60f92d5fe2 | ||
|
|
df27c2e1fc | ||
|
|
45f57939af | ||
|
|
30f5f66090 | ||
|
|
2a29303e3f | ||
|
|
643c60fd7a | ||
|
|
7174f857d8 | ||
|
|
0be2c0d40f | ||
|
|
a75c2194bc | ||
|
|
41140766f0 | ||
|
|
045c427317 | ||
|
|
ac404af48f | ||
|
|
a2ecd05240 | ||
|
|
a778a5ef81 | ||
|
|
c94ee7188c | ||
|
|
7ef6a02d0a | ||
|
|
c832cab8d0 | ||
|
|
234465789b | ||
|
|
da6d69d807 | ||
|
|
04ef7c5456 | ||
|
|
f05c3092b5 | ||
|
|
2637dc00da | ||
|
|
e8365d2c57 | ||
|
|
dd151b3f50 | ||
|
|
f1eeae8c71 | ||
|
|
41e4a74b57 | ||
|
|
fa49beb623 | ||
|
|
9bd206cedb | ||
|
|
5b01e4be2d | ||
|
|
bbbb40994d | ||
|
|
f709da5569 | ||
|
|
ccb17843c1 | ||
|
|
f8111db5ff | ||
|
|
3fcde8088c | ||
|
|
f937ae2c07 | ||
|
|
1c7c22352f | ||
|
|
0cae72b391 | ||
|
|
9a8c80f20a | ||
|
|
a42e3708aa | ||
|
|
c8fc8a0b65 | ||
|
|
8fc68a4b21 | ||
|
|
975d493b8a | ||
|
|
aee7a7a72b | ||
|
|
a31c24e5a0 | ||
|
|
943b0ff6ec | ||
|
|
d42c533fbb | ||
|
|
4dbde4cf7f | ||
|
|
7be8927d5e | ||
|
|
96d7743eab | ||
|
|
2254f76b30 | ||
|
|
852c1b7a27 | ||
|
|
522210adb6 | ||
|
|
a4089fcc72 | ||
|
|
60034b39a3 | ||
|
|
2f7c80a5e0 | ||
|
|
827e6e0dc0 | ||
|
|
dac1c9939e | ||
|
|
a6d74ea876 | ||
|
|
7a1d92a8d4 | ||
|
|
f2db76a0d5 | ||
|
|
3721632de2 | ||
|
|
4de949fe18 | ||
|
|
069b3fba37 | ||
|
|
e74dd47b1d | ||
|
|
6a9f57f83d | ||
|
|
80ff5c1f17 | ||
|
|
aa22047a0f | ||
|
|
abc941097c | ||
|
|
62630d6449 | ||
|
|
f228f022f5 | ||
|
|
1c1decfaf1 | ||
|
|
7786c83b0d | ||
|
|
41b75e6977 | ||
|
|
a54e1145a5 | ||
|
|
b8592a361c | ||
|
|
19881e4d7f | ||
|
|
126f9c0da3 | ||
|
|
7a140bf63c | ||
|
|
f5fedbb6b2 | ||
|
|
957cb2d56c | ||
|
|
b3cdc438ce | ||
|
|
22a3e73bac | ||
|
|
0ad91c43f7 | ||
|
|
2b208807a6 | ||
|
|
25f841d063 | ||
|
|
10b769c676 | ||
|
|
67da840097 | ||
|
|
93d4581721 | ||
|
|
0a7c5b0604 | ||
|
|
acbeb93f79 | ||
|
|
6565265bee | ||
|
|
b42a34d822 | ||
|
|
3ce3403b95 | ||
|
|
eb19980110 | ||
|
|
c7905c6638 | ||
|
|
fdd8ef5835 | ||
|
|
25bc8dd8a9 | ||
|
|
6512ab1351 | ||
|
|
5cd60fa5f9 | ||
|
|
fac571b51a | ||
|
|
9b87906a30 | ||
|
|
c8b1f00107 | ||
|
|
e4c49c37b9 | ||
|
|
268ca5b7f6 | ||
|
|
8697488126 | ||
|
|
dfcc2a1eb8 | ||
|
|
812f5084a1 | ||
|
|
6b46bae6c6 | ||
|
|
23e8c35918 | ||
|
|
ab60396ac4 | ||
|
|
343c275f46 | ||
|
|
12e41d783f | ||
|
|
2f891b4bfb | ||
|
|
170b86d0c6 | ||
|
|
07b7b743b4 | ||
|
|
64aff7b983 | ||
|
|
1299231a48 | ||
|
|
b6abb022f6 |
@@ -1,13 +1,23 @@
|
||||
./.*
|
||||
./*.md
|
||||
./*.yaml
|
||||
./apis
|
||||
./deploy
|
||||
./Dockerfile
|
||||
./generated/1.1*
|
||||
./hack/lib/tilt/
|
||||
./internal/mocks
|
||||
./LICENSE
|
||||
./site/
|
||||
./test
|
||||
**/*_test.go
|
||||
# This is effectively a copy of the .gitignore file.
|
||||
# The whole git repo, including the .git directory, should get copied into the Docker build context,
|
||||
# to enable the use of hack/get-ldflags.sh inside the Dockerfile.
|
||||
# When you change the .gitignore file, please consider also changing this file.
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# GoLand
|
||||
.idea
|
||||
|
||||
# MacOS Desktop Services Store
|
||||
.DS_Store
|
||||
|
||||
4
.github/codecov.yml
vendored
4
.github/codecov.yml
vendored
@@ -2,7 +2,7 @@ codecov:
|
||||
strict_yaml_branch: main
|
||||
require_ci_to_pass: no
|
||||
notify:
|
||||
wait_for_ci: no
|
||||
wait_for_ci: no
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
@@ -11,3 +11,5 @@ coverage:
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
ignore:
|
||||
- cmd/local-user-authenticator/
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
# When you change this file, please consider also changing the .dockerignore file.
|
||||
# See comments at the top of .dockerignore for more information.
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
@@ -11,11 +14,8 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# goland
|
||||
# GoLand
|
||||
.idea
|
||||
|
||||
# Intermediate files used by Tilt
|
||||
/hack/lib/tilt/build
|
||||
# MacOS Desktop Services Store
|
||||
.DS_Store
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# https://github.com/golangci/golangci-lint#config-file
|
||||
run:
|
||||
deadline: 1m
|
||||
skip-dirs:
|
||||
- generated
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
@@ -45,7 +47,6 @@ linters:
|
||||
- scopelint
|
||||
- sqlclosecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
exclude: '^(generated|hack/lib/tilt/tilt_modules)/'
|
||||
# This is a configuration for https://pre-commit.com/.
|
||||
# On macOS, try `brew install pre-commit` and then run `pre-commit install`.
|
||||
exclude: '^(site|generated)/'
|
||||
repos:
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
|
||||
35
ADOPTERS.md
35
ADOPTERS.md
@@ -1,9 +1,34 @@
|
||||
# Pinniped Adopters
|
||||
|
||||
These organizations are using Pinniped.
|
||||
If you're using Pinniped and want to add your organization to this
|
||||
list, [follow these directions](#adding-your-organization-to-the-list-of-adopters)!
|
||||
|
||||
* [VMware Tanzu](https://tanzu.vmware.com/) ([Tanzu Mission Control](https://tanzu.vmware.com/mission-control))
|
||||
## Organizations using Pinniped
|
||||
|
||||
If you are using Pinniped and are not on this list, you can open a [pull
|
||||
request](https://github.com/vmware-tanzu/pinniped/issues/new?template=feature-proposal.md)
|
||||
to add yourself.
|
||||
<a href="https://tanzu.vmware.com/tanzu" border="0" target="_blank"><img alt="vmware-tanzu" src="site/themes/pinniped/static/img/vmware-tanzu.svg" height="50"></a>
|
||||
|
||||
<a href="https://kubeapps.com/" border="0" target="_blank"><img alt="kubeapps" src="site/themes/pinniped/static/img/kubeapps.svg" height="50"></a>
|
||||
|
||||
<a href="https://www.ok.dk/" border="0" target="_blank"><img alt="ok-amba" src="site/themes/pinniped/static/img/ok-amba.svg" height="50"></a>
|
||||
|
||||
## Solutions built with Pinniped
|
||||
|
||||
Below is a list of solutions where Pinniped is being used as a component.
|
||||
|
||||
**[Kubeapps](https://kubeapps.com/)**
|
||||
|
||||
Kubeapps uses Pinniped to [enable SSO authentication](https://github.com/kubeapps/kubeapps/blob/master/docs/user/using-an-OIDC-provider-with-pinniped.md) when running on clusters where SSO cannot be configured for the cluster API server.
|
||||
|
||||
**[VMware Tanzu Kubernetes Grid (TKG)](https://tanzu.vmware.com/kubernetes-grid)**
|
||||
|
||||
TKG uses Pinniped to provide a seamless SSO experience across management and workload clusters.
|
||||
|
||||
**[VMware Tanzu Mission Control (TMC)](https://tanzu.vmware.com/mission-control)**
|
||||
|
||||
TMC uses Pinniped to provide a uniform authentication experience across all attached clusters.
|
||||
|
||||
## 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
|
||||
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.
|
||||
|
||||
@@ -8,17 +8,30 @@ Please see the [Code of Conduct](./CODE_OF_CONDUCT.md).
|
||||
|
||||
## Project Scope
|
||||
|
||||
Learn about the [scope](https://pinniped.dev/docs/scope/) of the project.
|
||||
See [SCOPE.md](./SCOPE.md) for some guidelines about what we consider in and out of scope for Pinniped.
|
||||
|
||||
## Roadmap
|
||||
|
||||
The near-term and mid-term roadmap for the work planned for the project [maintainers](MAINTAINERS.md) is documented in [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
## Community Meetings
|
||||
|
||||
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM PT. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
|
||||
Pinniped is better because of our contributors and [maintainers](MAINTAINERS.md). It is because of you that we can bring great
|
||||
software to the community. Please join us during our online community meetings,
|
||||
occurring every first and third Thursday of the month at 9 AM PT / 12 PM PT.
|
||||
Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09)
|
||||
to attend and add any agenda items you wish to discuss
|
||||
to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view).
|
||||
Join our [Google Group](https://groups.google.com/g/project-pinniped) to receive invites to this meeting.
|
||||
|
||||
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
|
||||
|
||||
## Discussion
|
||||
|
||||
Got a question, comment, or idea? Please don't hesitate to reach out via the GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions) tab at the top of this page or reach out in Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
|
||||
Got a question, comment, or idea? Please don't hesitate to reach out
|
||||
via GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions),
|
||||
GitHub [Issues](https://github.com/vmware-tanzu/pinniped/issues),
|
||||
or in the Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
|
||||
|
||||
## Issues
|
||||
|
||||
@@ -45,7 +58,7 @@ guidelines in the issue and pull request templates.
|
||||
To suggest a feature, please first open an
|
||||
[issue](https://github.com/vmware-tanzu/pinniped/issues/new?template=feature-proposal.md)
|
||||
and tag it with `proposal`, or create a new [Discussion](https://github.com/vmware-tanzu/pinniped/discussions).
|
||||
The project team will work with you on your feature request.
|
||||
The project [maintainers](MAINTAINERS.md) will work with you on your feature request.
|
||||
|
||||
Once the feature request has been validated, a [pull request](https://github.com/vmware-tanzu/pinniped/compare)
|
||||
can be opened to implement the feature.
|
||||
@@ -53,6 +66,10 @@ can be opened to implement the feature.
|
||||
For specifics on what to include in your feature request, please follow the
|
||||
guidelines in the issue and pull request templates.
|
||||
|
||||
### Reporting security vulnerabilities
|
||||
|
||||
Please follow the procedure described in [SECURITY.md](SECURITY.md).
|
||||
|
||||
## CLA
|
||||
|
||||
We welcome contributions from everyone but we can only accept them if you sign
|
||||
@@ -96,36 +113,29 @@ docker build .
|
||||
- [`kapp`](https://carvel.dev/#getting-started)
|
||||
- [`kind`](https://kind.sigs.k8s.io/docs/user/quick-start)
|
||||
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
|
||||
- [`tilt`](https://docs.tilt.dev/install.html)
|
||||
- [`ytt`](https://carvel.dev/#getting-started)
|
||||
|
||||
On macOS, these tools can be installed with [Homebrew](https://brew.sh/) (assuming you have Chrome installed already):
|
||||
|
||||
```bash
|
||||
brew install kind tilt-dev/tap/tilt k14s/tap/ytt k14s/tap/kapp kubectl chromedriver && brew cask install docker
|
||||
brew install kind k14s/tap/ytt k14s/tap/kapp kubectl chromedriver && brew cask install docker
|
||||
```
|
||||
|
||||
1. Create a local Kubernetes cluster using `kind`:
|
||||
1. Create a kind cluster, compile, create container images, and install Pinniped and supporting dependencies using:
|
||||
|
||||
```bash
|
||||
./hack/kind-up.sh
|
||||
./hack/prepare-for-integration-tests.sh
|
||||
```
|
||||
|
||||
1. Install Pinniped and supporting dependencies using `tilt`:
|
||||
|
||||
```bash
|
||||
./hack/tilt-up.sh
|
||||
```
|
||||
|
||||
Tilt will continue running and live-updating the Pinniped deployment whenever the code changes.
|
||||
|
||||
1. Run the Pinniped integration tests:
|
||||
|
||||
```bash
|
||||
source /tmp/integration-test-env && go test -v -count 1 ./test/integration
|
||||
source /tmp/integration-test-env && go test -v -count 1 -timeout 0 ./test/integration
|
||||
```
|
||||
|
||||
To uninstall the test environment, run `./hack/tilt-down.sh`.
|
||||
1. After making production code changes, recompile, redeploy, and run tests again by repeating the same
|
||||
commands described above. If there are only test code changes, then simply run the tests again.
|
||||
|
||||
To destroy the local Kubernetes cluster, run `./hack/kind-down.sh`.
|
||||
|
||||
### Observing Tests on the Continuous Integration Environment
|
||||
|
||||
32
Dockerfile
32
Dockerfile
@@ -3,7 +3,7 @@
|
||||
# Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
FROM golang:1.15.8 as build-env
|
||||
FROM golang:1.17.1 as build-env
|
||||
|
||||
WORKDIR /work
|
||||
COPY . .
|
||||
@@ -16,26 +16,18 @@ RUN \
|
||||
--mount=type=cache,target=/cache/gocache \
|
||||
--mount=type=cache,target=/cache/gomodcache \
|
||||
mkdir out && \
|
||||
GOCACHE=/cache/gocache \
|
||||
GOMODCACHE=/cache/gomodcache \
|
||||
CGO_ENABLED=0 \
|
||||
GOOS=linux \
|
||||
GOARCH=amd64 \
|
||||
go build -v -ldflags "$(hack/get-ldflags.sh)" -o out \
|
||||
./cmd/pinniped-concierge/... \
|
||||
./cmd/pinniped-supervisor/... \
|
||||
./cmd/local-user-authenticator/...
|
||||
export GOCACHE=/cache/gocache GOMODCACHE=/cache/gomodcache CGO_ENABLED=0 GOOS=linux GOARCH=amd64 && \
|
||||
go build -v -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-concierge-kube-cert-agent ./cmd/pinniped-concierge-kube-cert-agent/main.go && \
|
||||
go build -v -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-server ./cmd/pinniped-server/main.go && \
|
||||
ln -s /usr/local/bin/pinniped-server /usr/local/bin/pinniped-concierge && \
|
||||
ln -s /usr/local/bin/pinniped-server /usr/local/bin/pinniped-supervisor && \
|
||||
ln -s /usr/local/bin/pinniped-server /usr/local/bin/local-user-authenticator
|
||||
|
||||
# Use a Debian slim image to grab a reasonable default CA bundle.
|
||||
FROM debian:10.8-slim AS get-ca-bundle-env
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* /var/cache/debconf/*
|
||||
# Use a distroless runtime image with CA certificates, timezone data, and not much else.
|
||||
FROM gcr.io/distroless/static:nonroot@sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4
|
||||
|
||||
# Use a runtime image based on Debian slim.
|
||||
FROM debian:10.8-slim
|
||||
COPY --from=get-ca-bundle-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
# Copy the binaries from the build-env stage.
|
||||
COPY --from=build-env /work/out/ /usr/local/bin/
|
||||
# Copy the server binary from the build-env stage.
|
||||
COPY --from=build-env /usr/local/bin /usr/local/bin
|
||||
|
||||
# Document the ports
|
||||
EXPOSE 8080 8443
|
||||
@@ -44,4 +36,4 @@ EXPOSE 8080 8443
|
||||
USER 1001:1001
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["/usr/local/bin/pinniped-concierge"]
|
||||
ENTRYPOINT ["/usr/local/bin/pinniped-server"]
|
||||
|
||||
@@ -4,16 +4,21 @@ This is the current list of maintainers for the Pinniped project.
|
||||
|
||||
| Maintainer | GitHub ID | Affiliation |
|
||||
| --------------- | --------- | ----------- |
|
||||
| Andrew Keesler | [ankeesler](https://github.com/ankeesler) | [VMware](https://www.github.com/vmware/) |
|
||||
| Margo Crawford | [margocrawf](https://github.com/margocrawf) | [VMware](https://www.github.com/vmware/) |
|
||||
| Matt Moyer | [mattmoyer](https://github.com/mattmoyer) | [VMware](https://www.github.com/vmware/) |
|
||||
| Mo Khan | [enj](https://github.com/enj) | [VMware](https://www.github.com/vmware/) |
|
||||
| Pablo Schuhmacher | [pabloschuhmacher](https://github.com/pabloschuhmacher) | [VMware](https://www.github.com/vmware/) |
|
||||
| Anjali Telang | [anjaltelang](https://github.com/anjaltelang) | [VMware](https://www.github.com/vmware/) |
|
||||
| Ryan Richard | [cfryanr](https://github.com/cfryanr) | [VMware](https://www.github.com/vmware/) |
|
||||
|
||||
## Emeritus Maintainers
|
||||
|
||||
* Andrew Keesler, [ankeesler](https://github.com/ankeesler)
|
||||
* Pablo Schuhmacher, [pabloschuhmacher](https://github.com/pabloschuhmacher)
|
||||
* Matt Moyer, [mattmoyer](https://github.com/mattmoyer)
|
||||
|
||||
## Pinniped Contributors & Stakeholders
|
||||
|
||||
| Feature Area | Lead |
|
||||
| ----------------------------- | :---------------------: |
|
||||
| Technical Lead | Matt Moyer (mattmoyer) |
|
||||
| Product Management | Pablo Schuhmacher (pabloschuhmacher) |
|
||||
| Technical Lead | Mo Khan (enj) |
|
||||
| Product Management | Anjali Telang (anjaltelang) |
|
||||
| Community Management | Nanci Lancaster (microwavables) |
|
||||
|
||||
86
README.md
86
README.md
@@ -1,67 +1,53 @@
|
||||
<img src="site/content/docs/img/pinniped_logo.svg" alt="Pinniped Logo" width="100%"/>
|
||||
<a href="https://pinniped.dev" target="_blank">
|
||||
<img src="site/content/docs/img/pinniped_logo.svg" alt="Pinniped Logo" width="100%"/>
|
||||
</a>
|
||||
|
||||
## Overview
|
||||
|
||||
Pinniped provides identity services to Kubernetes.
|
||||
|
||||
Pinniped allows cluster administrators to easily plug in external identity
|
||||
providers (IDPs) into Kubernetes clusters. This is achieved via a uniform
|
||||
install procedure across all types and origins of Kubernetes clusters,
|
||||
declarative configuration via Kubernetes APIs, enterprise-grade integrations
|
||||
with IDPs, and distribution-specific integration strategies.
|
||||
- Easily plug in external identity providers into Kubernetes clusters while offering a simple install and configuration experience. Leverage first class integration with Kubernetes and kubectl command-line.
|
||||
- Give users a consistent, unified login experience across all your clusters, including on-premises and managed cloud environments.
|
||||
- Securely integrate with an enterprise IDP using standard protocols or use secure, externally managed identities instead of relying on simple, shared credentials.
|
||||
|
||||
### Example Use Cases
|
||||
To learn more, please visit the Pinniped project's website, https://pinniped.dev.
|
||||
|
||||
* Your team uses a large enterprise IDP, and has many clusters that they
|
||||
manage. Pinniped provides:
|
||||
* Seamless and robust integration with the IDP
|
||||
* Easy installation across clusters of any type and origin
|
||||
* A simplified login flow across all clusters
|
||||
* Your team shares a single cluster. Pinniped provides:
|
||||
* Simple configuration to integrate an IDP
|
||||
* Individual, revocable identities
|
||||
## Getting started with Pinniped
|
||||
|
||||
### Architecture
|
||||
|
||||
The Pinniped Supervisor component offers identity federation to enable a user to
|
||||
access multiple clusters with a single daily login to their external IDP. The
|
||||
Pinniped Supervisor supports various external [IDP
|
||||
types](https://github.com/vmware-tanzu/pinniped/tree/main/generated/1.20#k8s-api-idp-supervisor-pinniped-dev-v1alpha1).
|
||||
|
||||
The Pinniped Concierge component offers credential exchange to enable a user to
|
||||
exchange an external credential for a short-lived, cluster-specific
|
||||
credential. Pinniped supports various [authentication
|
||||
methods](https://github.com/vmware-tanzu/pinniped/tree/main/generated/1.20#authenticationconciergepinnipeddevv1alpha1)
|
||||
and implements different integration strategies for various Kubernetes
|
||||
distributions to make authentication possible.
|
||||
|
||||
The Pinniped Concierge can be configured to hook into the Pinniped Supervisor's
|
||||
federated credentials, or it can authenticate users directly via external IDP
|
||||
credentials.
|
||||
|
||||
To learn more, see [architecture](https://pinniped.dev/docs/architecture/).
|
||||
|
||||
<img src="site/content/docs/img/pinniped_architecture_concierge_supervisor.svg" alt="Pinniped Architecture Sketch"/>
|
||||
|
||||
## Trying Pinniped
|
||||
|
||||
Care to kick the tires? It's easy to [install and try Pinniped](https://pinniped.dev/docs/demo/).
|
||||
|
||||
## Community Meetings
|
||||
|
||||
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM PT. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
|
||||
|
||||
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
|
||||
Care to kick the tires? It's easy to [install and try Pinniped](https://pinniped.dev/docs/).
|
||||
|
||||
## Discussion
|
||||
|
||||
Got a question, comment, or idea? Please don't hesitate to reach out via the GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions) tab at the top of this page or reach out in Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
|
||||
Got a question, comment, or idea? Please don't hesitate to reach out
|
||||
via GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions),
|
||||
GitHub [Issues](https://github.com/vmware-tanzu/pinniped/issues),
|
||||
or in the Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome. Before contributing, please see the [contributing guide](CONTRIBUTING.md).
|
||||
Want to get involved? Contributions are welcome.
|
||||
|
||||
## Reporting Security Vulnerabilities
|
||||
Please see the [contributing guide](CONTRIBUTING.md) for more information about reporting bugs, requesting features,
|
||||
building and testing the code, submitting PRs, and other contributor topics.
|
||||
|
||||
## Community meetings
|
||||
|
||||
Pinniped is better because of our contributors and [maintainers](MAINTAINERS.md). It is because of you that we can bring great
|
||||
software to the community. Please join us during our online community meetings,
|
||||
occurring every first and third Thursday of the month at 9 AM PT / 12 PM PT.
|
||||
Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09)
|
||||
to attend and add any agenda items you wish to discuss
|
||||
to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view).
|
||||
Join our [Google Group](https://groups.google.com/g/project-pinniped) to receive invites to this meeting.
|
||||
|
||||
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
|
||||
|
||||
## Adopters
|
||||
|
||||
Some organizations and products using Pinniped are featured in [ADOPTERS.md](ADOPTERS.md).
|
||||
Add your own organization or product [here](https://github.com/vmware-tanzu/pinniped/discussions/152).
|
||||
|
||||
## Reporting security vulnerabilities
|
||||
|
||||
Please follow the procedure described in [SECURITY.md](SECURITY.md).
|
||||
|
||||
@@ -69,4 +55,4 @@ Please follow the procedure described in [SECURITY.md](SECURITY.md).
|
||||
|
||||
Pinniped is open source and licensed under Apache License Version 2.0. See [LICENSE](LICENSE).
|
||||
|
||||
Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
|
||||
53
ROADMAP.md
Normal file
53
ROADMAP.md
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
## **Pinniped Project Roadmap**
|
||||
|
||||
|
||||
###
|
||||
**About this document**
|
||||
|
||||
This document provides a link to the[ Pinniped Project issues](https://github.com/vmware-tanzu/pinniped/issues) list that serves as the up to date description of items that are in the Pinniped release pipeline. Most items are gathered from the community or include a feedback loop with the community. 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.
|
||||
|
||||
|
||||
###
|
||||
**How to help?**
|
||||
|
||||
Discussion on the roadmap can take place in threads under [Issues](https://github.com/vmware-tanzu/pinniped/issues) or in [community meetings](https://github.com/vmware-tanzu/pinniped/blob/main/CONTRIBUTING.md#meeting-with-the-maintainers). Please open and comment on an issue if you want to provide suggestions and feedback to an item in the roadmap. Please review the roadmap to avoid potential duplicated effort.
|
||||
|
||||
|
||||
###
|
||||
**Need an idea for a contribution?**
|
||||
|
||||
We’ve created an [Opportunity Areas](https://github.com/vmware-tanzu/pinniped/discussions/483) discussion thread that outlines some areas we believe are excellent starting points for the community to get involved. In that discussion we’ve included specific work items that one might consider that also support the high-level items presented in our roadmap.
|
||||
|
||||
|
||||
###
|
||||
**How to add an item to the roadmap?**
|
||||
|
||||
Please open an issue to track any initiative on the roadmap of Pinniped (usually driven by new feature requests). We will work with and rely on our community to focus our efforts to improve Pinniped.
|
||||
|
||||
|
||||
###
|
||||
**Current Roadmap**
|
||||
|
||||
The following table includes the current roadmap for Pinniped. If you have any questions or would like to contribute to Pinniped, please attend a [community meeting](https://github.com/vmware-tanzu/pinniped/blob/main/CONTRIBUTING.md#meeting-with-the-maintainers) to discuss with our team. If you don't know where to start, we are always looking for contributors that will help us reduce technical, automation, and documentation debt. Please take the timelines & dates as proposals and goals. Priorities and requirements change based on community feedback, roadblocks encountered, community contributions, etc. If you depend on a specific item, we encourage you to attend community meetings to get updated status information, or help us deliver that feature by contributing to Pinniped.
|
||||
|
||||
|
||||
|
||||
Last Updated: Sept 2021
|
||||
|Theme|Description|Timeline|
|
||||
|--|--|--|
|
||||
|Improving Security Posture|Supervisor token refresh fails when the upstream refresh token no longer works|Sept 2021|
|
||||
|Wider Concierge cluster support|Support for OpenShift cluster types in the Concierge|Sept 2021|
|
||||
|Multiple IDP support|Support multiple IDPs configured on a single Supervisor|Exploring/Ongoing|
|
||||
|Identity transforms|Support prefixing, filtering, or performing coarse-grained checks on upstream users and groups|Exploring/Ongoing|
|
||||
|CLI SSO|Support Kerberos based authentication on CLI |Exploring/Ongoing|
|
||||
|Extended IDP support|Support more types of identity providers on the Supervisor|Exploring/Ongoing|
|
||||
|Improved Documentation|Reorganizing and improving Pinniped docs; new how-to guides and tutorials|Exploring/Ongoing|
|
||||
|Improve our CI/CD systems|Upgrade tests; make Kind more efficient and reliable for CI ; Windows tests; performance tests; scale tests; soak tests|Exploring/Ongoing|
|
||||
|CLI Improvements|Improving CLI UX for setting up Supervisor IDPs|Exploring/Ongoing|
|
||||
|Telemetry|Adding some useful phone home metrics as well as some vanity metrics|Exploring/Ongoing|
|
||||
|Observability|Expose Pinniped metrics through Prometheus Integration|Exploring/Ongoing|
|
||||
|Device Code Flow|Add support for OAuth 2.0 Device Authorization Grant in the Pinniped CLI and Supervisor|Exploring/Ongoing|
|
||||
|Supervisor with New Clients|Enable registering new clients with Supervisor|Exploring/Ongoing|
|
||||
|
||||
|
||||
32
SCOPE.md
Normal file
32
SCOPE.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Project Scope
|
||||
|
||||
The Pinniped project is guided by the following principles.
|
||||
|
||||
- Pinniped lets you plug any external identity providers into Kubernetes.
|
||||
These integrations follow enterprise-grade security principles.
|
||||
- Pinniped is easy to install and use on any Kubernetes cluster via distribution-specific integration mechanisms.
|
||||
- Pinniped uses a declarative configuration via Kubernetes APIs.
|
||||
- Pinniped provides optimal user experience when authenticating to many clusters at one time.
|
||||
- Pinniped provides enterprise-grade security posture via secure defaults and revocable or very short-lived credentials.
|
||||
- Where possible, Pinniped will contribute ideas and code to upstream Kubernetes.
|
||||
|
||||
When contributing to Pinniped, please consider whether your contribution follows
|
||||
these guiding principles.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
The following items are out of scope for the Pinniped project.
|
||||
|
||||
- Authorization.
|
||||
- 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
|
||||
@@ -1,40 +1,144 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +kubebuilder:validation:Enum=KubeClusterSigningCertificate
|
||||
// StrategyType enumerates a type of "strategy" used to implement credential access on a cluster.
|
||||
// +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy
|
||||
type StrategyType string
|
||||
|
||||
// FrontendType enumerates a type of "frontend" used to provide access to users of a cluster.
|
||||
// +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy
|
||||
type FrontendType string
|
||||
|
||||
// StrategyStatus enumerates whether a strategy is working on a cluster.
|
||||
// +kubebuilder:validation:Enum=Success;Error
|
||||
type StrategyStatus string
|
||||
|
||||
// +kubebuilder:validation:Enum=FetchedKey;CouldNotFetchKey
|
||||
// StrategyReason enumerates the detailed reason why a strategy is in a particular status.
|
||||
// +kubebuilder:validation:Enum=Listening;Pending;Disabled;ErrorDuringSetup;CouldNotFetchKey;CouldNotGetClusterInfo;FetchedKey
|
||||
type StrategyReason string
|
||||
|
||||
const (
|
||||
KubeClusterSigningCertificateStrategyType = StrategyType("KubeClusterSigningCertificate")
|
||||
ImpersonationProxyStrategyType = StrategyType("ImpersonationProxy")
|
||||
|
||||
TokenCredentialRequestAPIFrontendType = FrontendType("TokenCredentialRequestAPI")
|
||||
ImpersonationProxyFrontendType = FrontendType("ImpersonationProxy")
|
||||
|
||||
SuccessStrategyStatus = StrategyStatus("Success")
|
||||
ErrorStrategyStatus = StrategyStatus("Error")
|
||||
|
||||
CouldNotFetchKeyStrategyReason = StrategyReason("CouldNotFetchKey")
|
||||
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
|
||||
ListeningStrategyReason = StrategyReason("Listening")
|
||||
PendingStrategyReason = StrategyReason("Pending")
|
||||
DisabledStrategyReason = StrategyReason("Disabled")
|
||||
ErrorDuringSetupStrategyReason = StrategyReason("ErrorDuringSetup")
|
||||
CouldNotFetchKeyStrategyReason = StrategyReason("CouldNotFetchKey")
|
||||
CouldNotGetClusterInfoStrategyReason = StrategyReason("CouldNotGetClusterInfo")
|
||||
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
|
||||
)
|
||||
|
||||
// Status of a credential issuer.
|
||||
// CredentialIssuerSpec describes the intended configuration of the Concierge.
|
||||
type CredentialIssuerSpec struct {
|
||||
// ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy.
|
||||
ImpersonationProxy *ImpersonationProxySpec `json:"impersonationProxy"`
|
||||
}
|
||||
|
||||
// ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy.
|
||||
//
|
||||
// +kubebuilder:validation:Enum=auto;enabled;disabled
|
||||
type ImpersonationProxyMode string
|
||||
|
||||
const (
|
||||
// ImpersonationProxyModeDisabled explicitly disables the impersonation proxy.
|
||||
ImpersonationProxyModeDisabled = ImpersonationProxyMode("disabled")
|
||||
|
||||
// ImpersonationProxyModeEnabled explicitly enables the impersonation proxy.
|
||||
ImpersonationProxyModeEnabled = ImpersonationProxyMode("enabled")
|
||||
|
||||
// ImpersonationProxyModeAuto enables or disables the impersonation proxy based upon the cluster in which it is running.
|
||||
ImpersonationProxyModeAuto = ImpersonationProxyMode("auto")
|
||||
)
|
||||
|
||||
// ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy.
|
||||
//
|
||||
// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP;None
|
||||
type ImpersonationProxyServiceType string
|
||||
|
||||
const (
|
||||
// ImpersonationProxyServiceTypeLoadBalancer provisions a service of type LoadBalancer.
|
||||
ImpersonationProxyServiceTypeLoadBalancer = ImpersonationProxyServiceType("LoadBalancer")
|
||||
|
||||
// ImpersonationProxyServiceTypeClusterIP provisions a service of type ClusterIP.
|
||||
ImpersonationProxyServiceTypeClusterIP = ImpersonationProxyServiceType("ClusterIP")
|
||||
|
||||
// ImpersonationProxyServiceTypeNone does not automatically provision any service.
|
||||
ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None")
|
||||
)
|
||||
|
||||
// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy.
|
||||
type ImpersonationProxySpec struct {
|
||||
// Mode configures whether the impersonation proxy should be started:
|
||||
// - "disabled" explicitly disables the impersonation proxy. This is the default.
|
||||
// - "enabled" explicitly enables the impersonation proxy.
|
||||
// - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running.
|
||||
Mode ImpersonationProxyMode `json:"mode"`
|
||||
|
||||
// Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients.
|
||||
//
|
||||
// +kubebuilder:default:={"type": "LoadBalancer"}
|
||||
Service ImpersonationProxyServiceSpec `json:"service"`
|
||||
|
||||
// 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".
|
||||
//
|
||||
// +optional
|
||||
ExternalEndpoint string `json:"externalEndpoint,omitempty"`
|
||||
}
|
||||
|
||||
// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy.
|
||||
type ImpersonationProxyServiceSpec struct {
|
||||
// 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.
|
||||
//
|
||||
// +kubebuilder:default:="LoadBalancer"
|
||||
Type ImpersonationProxyServiceType `json:"type,omitempty"`
|
||||
|
||||
// LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service.
|
||||
// This is not supported on all cloud providers.
|
||||
//
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=255
|
||||
// +optional
|
||||
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
|
||||
|
||||
// Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service.
|
||||
//
|
||||
// +optional
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// CredentialIssuerStatus describes the status of the Concierge.
|
||||
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"`
|
||||
}
|
||||
|
||||
// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer.
|
||||
// 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
|
||||
@@ -46,7 +150,7 @@ type CredentialIssuerKubeConfigInfo struct {
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData"`
|
||||
}
|
||||
|
||||
// Status of an integration strategy that was attempted by Pinniped.
|
||||
// CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped.
|
||||
type CredentialIssuerStrategy struct {
|
||||
// Type of integration attempted.
|
||||
Type StrategyType `json:"type"`
|
||||
@@ -63,9 +167,50 @@ type CredentialIssuerStrategy struct {
|
||||
|
||||
// When the status was last checked.
|
||||
LastUpdateTime metav1.Time `json:"lastUpdateTime"`
|
||||
|
||||
// Frontend describes how clients can connect using this strategy.
|
||||
Frontend *CredentialIssuerFrontend `json:"frontend,omitempty"`
|
||||
}
|
||||
|
||||
// Describes the configuration status of a Pinniped credential issuer.
|
||||
// CredentialIssuerFrontend describes how to connect using a particular integration strategy.
|
||||
type CredentialIssuerFrontend struct {
|
||||
// Type describes which frontend mechanism clients can use with a strategy.
|
||||
Type FrontendType `json:"type"`
|
||||
|
||||
// TokenCredentialRequestAPIInfo describes the parameters for the TokenCredentialRequest API on this Concierge.
|
||||
// This field is only set when Type is "TokenCredentialRequestAPI".
|
||||
TokenCredentialRequestAPIInfo *TokenCredentialRequestAPIInfo `json:"tokenCredentialRequestInfo,omitempty"`
|
||||
|
||||
// ImpersonationProxyInfo describes the parameters for the impersonation proxy on this Concierge.
|
||||
// This field is only set when Type is "ImpersonationProxy".
|
||||
ImpersonationProxyInfo *ImpersonationProxyInfo `json:"impersonationProxyInfo,omitempty"`
|
||||
}
|
||||
|
||||
// TokenCredentialRequestAPIInfo describes the parameters for the TokenCredentialRequest API on this Concierge.
|
||||
type TokenCredentialRequestAPIInfo struct {
|
||||
// Server is the Kubernetes API server URL.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:Pattern=`^https://|^http://`
|
||||
Server string `json:"server"`
|
||||
|
||||
// CertificateAuthorityData is the base64-encoded Kubernetes API server CA bundle.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData"`
|
||||
}
|
||||
|
||||
// ImpersonationProxyInfo describes the parameters for the impersonation proxy on this Concierge.
|
||||
type ImpersonationProxyInfo struct {
|
||||
// Endpoint is the HTTPS endpoint of the impersonation proxy.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:Pattern=`^https://`
|
||||
Endpoint string `json:"endpoint"`
|
||||
|
||||
// CertificateAuthorityData is the base64-encoded PEM CA bundle of the impersonation proxy.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData"`
|
||||
}
|
||||
|
||||
// CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
@@ -75,12 +220,18 @@ type CredentialIssuer struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Status of the credential issuer.
|
||||
// Spec describes the intended configuration of the Concierge.
|
||||
//
|
||||
// +optional
|
||||
Spec CredentialIssuerSpec `json:"spec"`
|
||||
|
||||
// CredentialIssuerStatus describes the status of the Concierge.
|
||||
//
|
||||
// +optional
|
||||
Status CredentialIssuerStatus `json:"status"`
|
||||
}
|
||||
|
||||
// List of CredentialIssuer objects.
|
||||
// CredentialIssuerList is a list of CredentialIssuer objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type CredentialIssuerList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
8
apis/concierge/identity/doc.go.tmpl
Normal file
8
apis/concierge/identity/doc.go.tmpl
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +groupName=identity.concierge.pinniped.dev
|
||||
|
||||
// Package identity is the internal version of the Pinniped identity API.
|
||||
package identity
|
||||
38
apis/concierge/identity/register.go.tmpl
Normal file
38
apis/concierge/identity/register.go.tmpl
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const GroupName = "identity.concierge.pinniped.dev"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects.
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns back a Group qualified GroupKind.
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns back a Group qualified GroupResource.
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&WhoAmIRequest{},
|
||||
&WhoAmIRequestList{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
37
apis/concierge/identity/types_userinfo.go.tmpl
Normal file
37
apis/concierge/identity/types_userinfo.go.tmpl
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
import "fmt"
|
||||
|
||||
// KubernetesUserInfo represents the current authenticated user, exactly as Kubernetes understands it.
|
||||
// Copied from the Kubernetes token review API.
|
||||
type KubernetesUserInfo struct {
|
||||
// User is the UserInfo associated with the current user.
|
||||
User UserInfo
|
||||
// Audiences are audience identifiers chosen by the authenticator.
|
||||
Audiences []string
|
||||
}
|
||||
|
||||
// UserInfo holds the information about the user needed to implement the
|
||||
// user.Info interface.
|
||||
type UserInfo struct {
|
||||
// The name that uniquely identifies this user among all active users.
|
||||
Username string
|
||||
// A unique value that identifies this user across time. If this user is
|
||||
// deleted and another user by the same name is added, they will have
|
||||
// different UIDs.
|
||||
UID string
|
||||
// The names of groups this user is a part of.
|
||||
Groups []string
|
||||
// Any additional information provided by the authenticator.
|
||||
Extra map[string]ExtraValue
|
||||
}
|
||||
|
||||
// ExtraValue masks the value so protobuf can generate
|
||||
type ExtraValue []string
|
||||
|
||||
func (t ExtraValue) String() string {
|
||||
return fmt.Sprintf("%v", []string(t))
|
||||
}
|
||||
40
apis/concierge/identity/types_whoami.go.tmpl
Normal file
40
apis/concierge/identity/types_whoami.go.tmpl
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// WhoAmIRequest submits a request to echo back the current authenticated user.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type WhoAmIRequest struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ObjectMeta
|
||||
|
||||
Spec WhoAmIRequestSpec
|
||||
Status WhoAmIRequestStatus
|
||||
}
|
||||
|
||||
type WhoAmIRequestSpec struct {
|
||||
// empty for now but we may add some config here in the future
|
||||
// any such config must be safe in the context of an unauthenticated user
|
||||
}
|
||||
|
||||
type WhoAmIRequestStatus struct {
|
||||
// The current authenticated user, exactly as Kubernetes understands it.
|
||||
KubernetesUserInfo KubernetesUserInfo
|
||||
|
||||
// We may add concierge specific information here in the future.
|
||||
}
|
||||
|
||||
// WhoAmIRequestList is a list of WhoAmIRequest objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type WhoAmIRequestList struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
|
||||
// Items is a list of WhoAmIRequest
|
||||
Items []WhoAmIRequest
|
||||
}
|
||||
4
apis/concierge/identity/v1alpha1/conversion.go.tmpl
Normal file
4
apis/concierge/identity/v1alpha1/conversion.go.tmpl
Normal file
@@ -0,0 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
12
apis/concierge/identity/v1alpha1/defaults.go.tmpl
Normal file
12
apis/concierge/identity/v1alpha1/defaults.go.tmpl
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
}
|
||||
11
apis/concierge/identity/v1alpha1/doc.go.tmpl
Normal file
11
apis/concierge/identity/v1alpha1/doc.go.tmpl
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2021 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
|
||||
// +groupName=identity.concierge.pinniped.dev
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the Pinniped identity API.
|
||||
package v1alpha1
|
||||
43
apis/concierge/identity/v1alpha1/register.go.tmpl
Normal file
43
apis/concierge/identity/v1alpha1/register.go.tmpl
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const GroupName = "identity.concierge.pinniped.dev"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects.
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
||||
|
||||
var (
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
|
||||
}
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&WhoAmIRequest{},
|
||||
&WhoAmIRequestList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource.
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
41
apis/concierge/identity/v1alpha1/types_userinfo.go.tmpl
Normal file
41
apis/concierge/identity/v1alpha1/types_userinfo.go.tmpl
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import "fmt"
|
||||
|
||||
// KubernetesUserInfo represents the current authenticated user, exactly as Kubernetes understands it.
|
||||
// Copied from the Kubernetes token review API.
|
||||
type KubernetesUserInfo struct {
|
||||
// User is the UserInfo associated with the current user.
|
||||
User UserInfo `json:"user"`
|
||||
// Audiences are audience identifiers chosen by the authenticator.
|
||||
// +optional
|
||||
Audiences []string `json:"audiences,omitempty"`
|
||||
}
|
||||
|
||||
// UserInfo holds the information about the user needed to implement the
|
||||
// user.Info interface.
|
||||
type UserInfo struct {
|
||||
// The name that uniquely identifies this user among all active users.
|
||||
Username string `json:"username"`
|
||||
// A unique value that identifies this user across time. If this user is
|
||||
// deleted and another user by the same name is added, they will have
|
||||
// different UIDs.
|
||||
// +optional
|
||||
UID string `json:"uid,omitempty"`
|
||||
// The names of groups this user is a part of.
|
||||
// +optional
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
// Any additional information provided by the authenticator.
|
||||
// +optional
|
||||
Extra map[string]ExtraValue `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
// ExtraValue masks the value so protobuf can generate
|
||||
type ExtraValue []string
|
||||
|
||||
func (t ExtraValue) String() string {
|
||||
return fmt.Sprintf("%v", []string(t))
|
||||
}
|
||||
43
apis/concierge/identity/v1alpha1/types_whoami.go.tmpl
Normal file
43
apis/concierge/identity/v1alpha1/types_whoami.go.tmpl
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// WhoAmIRequest submits a request to echo back the current authenticated user.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +genclient:onlyVerbs=create
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type WhoAmIRequest struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec WhoAmIRequestSpec `json:"spec,omitempty"`
|
||||
Status WhoAmIRequestStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type WhoAmIRequestSpec struct {
|
||||
// empty for now but we may add some config here in the future
|
||||
// any such config must be safe in the context of an unauthenticated user
|
||||
}
|
||||
|
||||
type WhoAmIRequestStatus struct {
|
||||
// The current authenticated user, exactly as Kubernetes understands it.
|
||||
KubernetesUserInfo KubernetesUserInfo `json:"kubernetesUserInfo"`
|
||||
|
||||
// We may add concierge specific information here in the future.
|
||||
}
|
||||
|
||||
// WhoAmIRequestList is a list of WhoAmIRequest objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type WhoAmIRequestList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Items is a list of WhoAmIRequest
|
||||
Items []WhoAmIRequest `json:"items"`
|
||||
}
|
||||
14
apis/concierge/identity/validation/validation.go.tmpl
Normal file
14
apis/concierge/identity/validation/validation.go.tmpl
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
identityapi "go.pinniped.dev/GENERATED_PKG/apis/concierge/identity"
|
||||
)
|
||||
|
||||
func ValidateWhoAmIRequest(whoAmIRequest *identityapi.WhoAmIRequest) field.ErrorList {
|
||||
return nil // add validation for spec here if we expand it
|
||||
}
|
||||
@@ -31,6 +31,7 @@ type TokenCredentialRequestStatus struct {
|
||||
// TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchange for a cluster-specific credential.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +genclient:onlyVerbs=create
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type TokenCredentialRequest struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -32,6 +32,10 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&OIDCIdentityProvider{},
|
||||
&OIDCIdentityProviderList{},
|
||||
&LDAPIdentityProvider{},
|
||||
&LDAPIdentityProviderList{},
|
||||
&ActiveDirectoryIdentityProvider{},
|
||||
&ActiveDirectoryIdentityProviderList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ActiveDirectoryIdentityProviderPhase string
|
||||
|
||||
const (
|
||||
// ActiveDirectoryPhasePending is the default phase for newly-created ActiveDirectoryIdentityProvider resources.
|
||||
ActiveDirectoryPhasePending ActiveDirectoryIdentityProviderPhase = "Pending"
|
||||
|
||||
// ActiveDirectoryPhaseReady is the phase for an ActiveDirectoryIdentityProvider resource in a healthy state.
|
||||
ActiveDirectoryPhaseReady ActiveDirectoryIdentityProviderPhase = "Ready"
|
||||
|
||||
// ActiveDirectoryPhaseError is the phase for an ActiveDirectoryIdentityProvider in an unhealthy state.
|
||||
ActiveDirectoryPhaseError ActiveDirectoryIdentityProviderPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of an Active Directory identity provider.
|
||||
type ActiveDirectoryIdentityProviderStatus struct {
|
||||
// Phase summarizes the overall status of the ActiveDirectoryIdentityProvider.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase ActiveDirectoryIdentityProviderPhase `json:"phase,omitempty"`
|
||||
|
||||
// Represents the observations of an identity provider's current state.
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderBind struct {
|
||||
// SecretName contains the name of a namespace-local Secret object that provides the username and
|
||||
// password for an Active Directory bind user. This account will be used to perform LDAP searches. The Secret should be
|
||||
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
|
||||
// should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
|
||||
// The password must be non-empty.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
SecretName string `json:"secretName"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderUserSearchAttributes struct {
|
||||
// Username specifies the name of the attribute in Active Directory entry whose value shall become the username
|
||||
// of the user after a successful authentication.
|
||||
// Optional, when empty this defaults to "userPrincipalName".
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
|
||||
// UID specifies the name of the attribute in the ActiveDirectory entry which whose value shall be used to uniquely
|
||||
// identify the user within this ActiveDirectory provider after a successful authentication.
|
||||
// Optional, when empty this defaults to "objectGUID".
|
||||
// +optional
|
||||
UID string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderGroupSearchAttributes struct {
|
||||
// GroupName specifies the name of the attribute in the Active Directory entries whose value shall become a group name
|
||||
// in the user's list of groups after a successful authentication.
|
||||
// The value of this field is case-sensitive and must match the case of the attribute name returned by the ActiveDirectory
|
||||
// server in the user's entry. E.g. "cn" for common name. Distinguished names can be used by specifying lower-case "dn".
|
||||
// Optional. When not specified, this defaults to a custom field that looks like "sAMAccountName@domain",
|
||||
// where domain is constructed from the domain components of the group DN.
|
||||
// +optional
|
||||
GroupName string `json:"groupName,omitempty"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderUserSearch struct {
|
||||
// Base is the dn (distinguished name) that should be used as the search base when searching for users.
|
||||
// E.g. "ou=users,dc=example,dc=com".
|
||||
// Optional, when not specified it will be based on the result of a query for the defaultNamingContext
|
||||
// (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
// The default behavior searches your entire domain for users.
|
||||
// It may make sense to specify a subtree as a search base if you wish to exclude some users
|
||||
// or to make searches faster.
|
||||
// +optional
|
||||
Base string `json:"base,omitempty"`
|
||||
|
||||
// Filter is the search filter which should be applied when searching for users. The pattern "{}" must occur
|
||||
// in the filter at least once and will be dynamically replaced by the username for which the search is being run.
|
||||
// E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see
|
||||
// https://ldap.com/ldap-filters.
|
||||
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
|
||||
// Optional. When not specified, the default will be
|
||||
// '(&(objectClass=person)(!(objectClass=computer))(!(showInAdvancedViewOnly=TRUE))(|(sAMAccountName={}")(mail={})(userPrincipalName={})(sAMAccountType=805306368))'
|
||||
// This means that the user is a person, is not a computer, the sAMAccountType is for a normal user account,
|
||||
// and is not shown in advanced view only
|
||||
// (which would likely mean its a system created service account with advanced permissions).
|
||||
// Also, either the sAMAccountName, the userPrincipalName, or the mail attribute matches the input username.
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// Attributes specifies how the user's information should be read from the ActiveDirectory entry which was found as
|
||||
// the result of the user search.
|
||||
// +optional
|
||||
Attributes ActiveDirectoryIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderGroupSearch struct {
|
||||
// Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g.
|
||||
// "ou=groups,dc=example,dc=com".
|
||||
// Optional, when not specified it will be based on the result of a query for the defaultNamingContext
|
||||
// (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
// The default behavior searches your entire domain for groups.
|
||||
// It may make sense to specify a subtree as a search base if you wish to exclude some groups
|
||||
// for security reasons or to make searches faster.
|
||||
// +optional
|
||||
Base string `json:"base,omitempty"`
|
||||
|
||||
// Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user.
|
||||
// The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the
|
||||
// dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or
|
||||
// "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see
|
||||
// https://ldap.com/ldap-filters.
|
||||
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
|
||||
// Optional. When not specified, the default will act as if the filter were specified as
|
||||
// "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})".
|
||||
// This searches nested groups by default.
|
||||
// Note that nested group search can be slow for some Active Directory servers. To disable it,
|
||||
// you can set the filter to
|
||||
// "(&(objectClass=group)(member={})"
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as
|
||||
// the result of the group search.
|
||||
// +optional
|
||||
Attributes ActiveDirectoryIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring an ActiveDirectory identity provider.
|
||||
type ActiveDirectoryIdentityProviderSpec struct {
|
||||
// Host is the hostname of this Active Directory identity provider, i.e., where to connect. For example: ldap.example.com:636.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Host string `json:"host"`
|
||||
|
||||
// TLS contains the connection settings for how to establish the connection to the Host.
|
||||
TLS *TLSSpec `json:"tls,omitempty"`
|
||||
|
||||
// Bind contains the configuration for how to provide access credentials during an initial bind to the ActiveDirectory server
|
||||
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
|
||||
Bind ActiveDirectoryIdentityProviderBind `json:"bind,omitempty"`
|
||||
|
||||
// UserSearch contains the configuration for searching for a user by name in Active Directory.
|
||||
UserSearch ActiveDirectoryIdentityProviderUserSearch `json:"userSearch,omitempty"`
|
||||
|
||||
// GroupSearch contains the configuration for searching for a user's group membership in ActiveDirectory.
|
||||
GroupSearch ActiveDirectoryIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
|
||||
}
|
||||
|
||||
// ActiveDirectoryIdentityProvider describes the configuration of an upstream Microsoft Active Directory identity provider.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
|
||||
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
|
||||
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type ActiveDirectoryIdentityProvider struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec for configuring the identity provider.
|
||||
Spec ActiveDirectoryIdentityProviderSpec `json:"spec"`
|
||||
|
||||
// Status of the identity provider.
|
||||
Status ActiveDirectoryIdentityProviderStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// List of ActiveDirectoryIdentityProvider objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type ActiveDirectoryIdentityProviderList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []ActiveDirectoryIdentityProvider `json:"items"`
|
||||
}
|
||||
171
apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go.tmpl
Normal file
171
apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go.tmpl
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type LDAPIdentityProviderPhase string
|
||||
|
||||
const (
|
||||
// LDAPPhasePending is the default phase for newly-created LDAPIdentityProvider resources.
|
||||
LDAPPhasePending LDAPIdentityProviderPhase = "Pending"
|
||||
|
||||
// LDAPPhaseReady is the phase for an LDAPIdentityProvider resource in a healthy state.
|
||||
LDAPPhaseReady LDAPIdentityProviderPhase = "Ready"
|
||||
|
||||
// LDAPPhaseError is the phase for an LDAPIdentityProvider in an unhealthy state.
|
||||
LDAPPhaseError LDAPIdentityProviderPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of an LDAP identity provider.
|
||||
type LDAPIdentityProviderStatus struct {
|
||||
// Phase summarizes the overall status of the LDAPIdentityProvider.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase LDAPIdentityProviderPhase `json:"phase,omitempty"`
|
||||
|
||||
// Represents the observations of an identity provider's current state.
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
type LDAPIdentityProviderBind struct {
|
||||
// SecretName contains the name of a namespace-local Secret object that provides the username and
|
||||
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
|
||||
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
|
||||
// should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
|
||||
// The password must be non-empty.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
SecretName string `json:"secretName"`
|
||||
}
|
||||
|
||||
type LDAPIdentityProviderUserSearchAttributes struct {
|
||||
// Username specifies the name of the attribute in the LDAP entry whose value shall become the username
|
||||
// of the user after a successful authentication. This would typically be the same attribute name used in
|
||||
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
|
||||
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
|
||||
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field
|
||||
// is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default
|
||||
// value of "dn={}" would not work.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Username string `json:"username,omitempty"`
|
||||
|
||||
// UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely
|
||||
// identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID".
|
||||
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
|
||||
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
UID string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
type LDAPIdentityProviderGroupSearchAttributes struct {
|
||||
// GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name
|
||||
// in the user's list of groups after a successful authentication.
|
||||
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
|
||||
// server in the user's entry. E.g. "cn" for common name. Distinguished names can be used by specifying lower-case "dn".
|
||||
// Optional. When not specified, the default will act as if the GroupName were specified as "dn" (distinguished name).
|
||||
// +optional
|
||||
GroupName string `json:"groupName,omitempty"`
|
||||
}
|
||||
|
||||
type LDAPIdentityProviderUserSearch struct {
|
||||
// Base is the dn (distinguished name) that should be used as the search base when searching for users.
|
||||
// E.g. "ou=users,dc=example,dc=com".
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Base string `json:"base,omitempty"`
|
||||
|
||||
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
|
||||
// in the filter at least once and will be dynamically replaced by the username for which the search is being run.
|
||||
// E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see
|
||||
// https://ldap.com/ldap-filters.
|
||||
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
|
||||
// Optional. When not specified, the default will act as if the Filter were specified as the value from
|
||||
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
|
||||
// explicitly specified, since the default value of "dn={}" would not work.
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// Attributes specifies how the user's information should be read from the LDAP entry which was found as
|
||||
// the result of the user search.
|
||||
// +optional
|
||||
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
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.
|
||||
// +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.
|
||||
// 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"`
|
||||
|
||||
// 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
|
||||
Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring an LDAP identity provider.
|
||||
type LDAPIdentityProviderSpec struct {
|
||||
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Host string `json:"host"`
|
||||
|
||||
// TLS contains the connection settings for how to establish the connection to the Host.
|
||||
TLS *TLSSpec `json:"tls,omitempty"`
|
||||
|
||||
// Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server
|
||||
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
|
||||
Bind LDAPIdentityProviderBind `json:"bind,omitempty"`
|
||||
|
||||
// UserSearch contains the configuration for searching for a user by name in the LDAP provider.
|
||||
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
|
||||
|
||||
// GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
|
||||
GroupSearch LDAPIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
|
||||
}
|
||||
|
||||
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access
|
||||
// Protocol (LDAP) identity provider.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
|
||||
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
|
||||
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type LDAPIdentityProvider struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec for configuring the identity provider.
|
||||
Spec LDAPIdentityProviderSpec `json:"spec"`
|
||||
|
||||
// Status of the identity provider.
|
||||
Status LDAPIdentityProviderStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// List of LDAPIdentityProvider objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type LDAPIdentityProviderList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []LDAPIdentityProvider `json:"items"`
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -39,9 +39,31 @@ type OIDCIdentityProviderStatus struct {
|
||||
// request parameters.
|
||||
type OIDCAuthorizationConfig struct {
|
||||
// AdditionalScopes are the scopes in addition to "openid" that will be requested as part of the authorization
|
||||
// request flow with an OIDC identity provider. By default only the "openid" scope will be requested.
|
||||
// request flow with an OIDC identity provider.
|
||||
// In the case of a Resource Owner Password Credentials Grant flow, AdditionalScopes are the scopes
|
||||
// in addition to "openid" that will be requested as part of the token request (see also the allowPasswordGrant field).
|
||||
// By default, only the "openid" scope will be requested.
|
||||
// +optional
|
||||
AdditionalScopes []string `json:"additionalScopes,omitempty"`
|
||||
|
||||
// AllowPasswordGrant, when true, will allow the use of OAuth 2.0's Resource Owner Password Credentials Grant
|
||||
// (see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3) to authenticate to the OIDC provider using a
|
||||
// username and password without a web browser, in addition to the usual browser-based OIDC Authorization Code Flow.
|
||||
// The Resource Owner Password Credentials Grant is not officially part of the OIDC specification, so it may not be
|
||||
// supported by your OIDC provider. If your OIDC provider supports returning ID tokens from a Resource Owner Password
|
||||
// Credentials Grant token request, then you can choose to set this field to true. This will allow end users to choose
|
||||
// to present their username and password to the kubectl CLI (using the Pinniped plugin) to authenticate to the
|
||||
// cluster, without using a web browser to log in as is customary in OIDC Authorization Code Flow. This may be
|
||||
// convenient for users, especially for identities from your OIDC provider which are not intended to represent a human
|
||||
// actor, such as service accounts performing actions in a CI/CD environment. Even if your OIDC provider supports it,
|
||||
// you may wish to disable this behavior by setting this field to false when you prefer to only allow users of this
|
||||
// OIDCIdentityProvider to log in via the browser-based OIDC Authorization Code Flow. Using the Resource Owner Password
|
||||
// Credentials Grant means that the Pinniped CLI and Pinniped Supervisor will directly handle your end users' passwords
|
||||
// (similar to LDAPIdentityProvider), and you will not be able to require multi-factor authentication or use the other
|
||||
// web-based login features of your OIDC provider during Resource Owner Password Credentials Grant logins.
|
||||
// AllowPasswordGrant defaults to false.
|
||||
// +optional
|
||||
AllowPasswordGrant bool `json:"allowPasswordGrant,omitempty"`
|
||||
}
|
||||
|
||||
// OIDCClaims provides a mapping from upstream claims into identities.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// IDPType are the strings that can be returned by the Supervisor identity provider discovery endpoint
|
||||
// as the "type" of each returned identity provider.
|
||||
type IDPType string
|
||||
|
||||
// IDPFlow are the strings that can be returned by the Supervisor identity provider discovery endpoint
|
||||
// in the array of allowed client "flows" for each returned identity provider.
|
||||
type IDPFlow string
|
||||
|
||||
const (
|
||||
IDPTypeOIDC IDPType = "oidc"
|
||||
IDPTypeLDAP IDPType = "ldap"
|
||||
IDPTypeActiveDirectory IDPType = "activedirectory"
|
||||
|
||||
IDPFlowCLIPassword IDPFlow = "cli_password"
|
||||
IDPFlowBrowserAuthcode IDPFlow = "browser_authcode"
|
||||
)
|
||||
|
||||
// Equals is a convenience function for comparing an IDPType to a string.
|
||||
func (r IDPType) Equals(s string) bool {
|
||||
return string(r) == s
|
||||
}
|
||||
|
||||
// String is a convenience function to convert an IDPType to a string.
|
||||
func (r IDPType) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
// Equals is a convenience function for comparing an IDPFlow to a string.
|
||||
func (r IDPFlow) Equals(s string) bool {
|
||||
return string(r) == s
|
||||
}
|
||||
|
||||
// String is a convenience function to convert an IDPFlow to a string.
|
||||
func (r IDPFlow) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
// OIDCDiscoveryResponse is part of the response from a FederationDomain's OpenID Provider Configuration
|
||||
// Document returned by the .well-known/openid-configuration endpoint. It ignores all the standard OpenID Provider
|
||||
// configuration metadata and only picks out the portion related to Supervisor identity provider discovery.
|
||||
type OIDCDiscoveryResponse struct {
|
||||
SupervisorDiscovery OIDCDiscoveryResponseIDPEndpoint `json:"discovery.supervisor.pinniped.dev/v1alpha1"`
|
||||
}
|
||||
|
||||
// OIDCDiscoveryResponseIDPEndpoint contains the URL for the identity provider discovery endpoint.
|
||||
type OIDCDiscoveryResponseIDPEndpoint struct {
|
||||
PinnipedIDPsEndpoint string `json:"pinniped_identity_providers_endpoint"`
|
||||
}
|
||||
|
||||
// IDPDiscoveryResponse is the response of a FederationDomain's identity provider discovery endpoint.
|
||||
type IDPDiscoveryResponse struct {
|
||||
PinnipedIDPs []PinnipedIDP `json:"pinniped_identity_providers"`
|
||||
}
|
||||
|
||||
// PinnipedIDP describes a single identity provider as included in the response of a FederationDomain's
|
||||
// identity provider discovery endpoint.
|
||||
type PinnipedIDP struct {
|
||||
Name string `json:"name"`
|
||||
Type IDPType `json:"type"`
|
||||
Flows []IDPFlow `json:"flows,omitempty"`
|
||||
}
|
||||
25
apis/supervisor/oidc/types_supervisor_oidc.go.tmpl
Normal file
25
apis/supervisor/oidc/types_supervisor_oidc.go.tmpl
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package oidc
|
||||
|
||||
// Constants related to the Supervisor FederationDomain's authorization and token endpoints.
|
||||
const (
|
||||
// AuthorizeUsernameHeaderName is the name of the HTTP header which can be used to transmit a username
|
||||
// to the authorize endpoint when using a password flow, for example an OIDCIdentityProvider with a password grant
|
||||
// or an LDAPIdentityProvider.
|
||||
AuthorizeUsernameHeaderName = "Pinniped-Username"
|
||||
|
||||
// AuthorizePasswordHeaderName is the name of the HTTP header which can be used to transmit a password
|
||||
// to the authorize endpoint when using a password flow, for example an OIDCIdentityProvider with a password grant
|
||||
// or an LDAPIdentityProvider.
|
||||
AuthorizePasswordHeaderName = "Pinniped-Password" //nolint:gosec // this is not a credential
|
||||
|
||||
// AuthorizeUpstreamIDPNameParamName is the name of the HTTP request parameter which can be used to help select which
|
||||
// identity provider should be used for authentication by sending the name of the desired identity provider.
|
||||
AuthorizeUpstreamIDPNameParamName = "pinniped_idp_name"
|
||||
|
||||
// AuthorizeUpstreamIDPTypeParamName is the name of the HTTP request parameter which can be used to help select which
|
||||
// identity provider should be used for authentication by sending the type of the desired identity provider.
|
||||
AuthorizeUpstreamIDPTypeParamName = "pinniped_idp_type"
|
||||
)
|
||||
55
cmd/pinniped-concierge-kube-cert-agent/main.go
Normal file
55
cmd/pinniped-concierge-kube-cert-agent/main.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main is the combined entrypoint for the Pinniped "kube-cert-agent" component.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals // these are swapped during unit tests.
|
||||
var (
|
||||
getenv = os.Getenv
|
||||
fail = log.Fatalf
|
||||
sleep = time.Sleep
|
||||
out = io.Writer(os.Stdout)
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fail("missing subcommand")
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "sleep":
|
||||
sleep(math.MaxInt64)
|
||||
case "print":
|
||||
certBytes, err := ioutil.ReadFile(getenv("CERT_PATH"))
|
||||
if err != nil {
|
||||
fail("could not read CERT_PATH: %v", err)
|
||||
}
|
||||
keyBytes, err := ioutil.ReadFile(getenv("KEY_PATH"))
|
||||
if err != nil {
|
||||
fail("could not read KEY_PATH: %v", err)
|
||||
}
|
||||
if err := json.NewEncoder(out).Encode(&struct {
|
||||
Cert string `json:"tls.crt"`
|
||||
Key string `json:"tls.key"`
|
||||
}{
|
||||
Cert: base64.StdEncoding.EncodeToString(certBytes),
|
||||
Key: base64.StdEncoding.EncodeToString(keyBytes),
|
||||
}); err != nil {
|
||||
fail("failed to write output: %v", err)
|
||||
}
|
||||
default:
|
||||
fail("invalid subcommand %q", os.Args[1])
|
||||
}
|
||||
}
|
||||
128
cmd/pinniped-concierge-kube-cert-agent/main_test.go
Normal file
128
cmd/pinniped-concierge-kube-cert-agent/main_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type errWriter struct{}
|
||||
|
||||
func (e errWriter) Write([]byte) (int, error) { return 0, fmt.Errorf("some write error") }
|
||||
|
||||
func TestEntrypoint(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
args []string
|
||||
env map[string]string
|
||||
failOutput bool
|
||||
wantSleep time.Duration
|
||||
wantLog string
|
||||
wantOutJSON string
|
||||
wantFail bool
|
||||
}{
|
||||
{
|
||||
name: "missing args",
|
||||
args: []string{},
|
||||
wantLog: "missing subcommand\n",
|
||||
wantFail: true,
|
||||
},
|
||||
{
|
||||
name: "invalid subcommand",
|
||||
args: []string{"/path/to/binary", "invalid"},
|
||||
wantLog: "invalid subcommand \"invalid\"\n",
|
||||
wantFail: true,
|
||||
},
|
||||
{
|
||||
name: "valid sleep",
|
||||
args: []string{"/path/to/binary", "sleep"},
|
||||
wantSleep: 2562047*time.Hour + 47*time.Minute + 16*time.Second + 854775807*time.Nanosecond, // math.MaxInt64 nanoseconds, approximately 290 years
|
||||
},
|
||||
{
|
||||
name: "missing cert file",
|
||||
args: []string{"/path/to/binary", "print"},
|
||||
env: map[string]string{
|
||||
"CERT_PATH": "./does/not/exist",
|
||||
"KEY_PATH": "./testdata/test.key",
|
||||
},
|
||||
wantFail: true,
|
||||
wantLog: "could not read CERT_PATH: open ./does/not/exist: no such file or directory\n",
|
||||
},
|
||||
{
|
||||
name: "missing key file",
|
||||
args: []string{"/path/to/binary", "print"},
|
||||
env: map[string]string{
|
||||
"CERT_PATH": "./testdata/test.crt",
|
||||
"KEY_PATH": "./does/not/exist",
|
||||
},
|
||||
wantFail: true,
|
||||
wantLog: "could not read KEY_PATH: open ./does/not/exist: no such file or directory\n",
|
||||
},
|
||||
{
|
||||
name: "fail to write output",
|
||||
args: []string{"/path/to/binary", "print"},
|
||||
env: map[string]string{
|
||||
"CERT_PATH": "./testdata/test.crt",
|
||||
"KEY_PATH": "./testdata/test.key",
|
||||
},
|
||||
failOutput: true,
|
||||
wantFail: true,
|
||||
wantLog: "failed to write output: some write error\n",
|
||||
},
|
||||
{
|
||||
name: "successful print",
|
||||
args: []string{"/path/to/binary", "print"},
|
||||
env: map[string]string{
|
||||
"CERT_PATH": "./testdata/test.crt",
|
||||
"KEY_PATH": "./testdata/test.key",
|
||||
},
|
||||
wantOutJSON: `{
|
||||
"tls.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EY3lOVEl4TURReE9Gb1hEVE13TURjeU16SXhNRFF4T0Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTDNLCmhZdjJnSVExRHd6aDJjV01pZCtvZkFudkxJZlYyWHY2MXZUTEdwclVJK1hVcUI0L2d0ZjZYNlVObjBMZXR0Mm4KZDhwNHd5N2h3NzNoVS9nZ2R2bVdKdnFCclNqYzNKR2Z5K2tqNjZmS1hYK1BUbGJMN1Fid2lSdmNTcUlYSVdsVgpsSEh4RUNXckVEOGpDdWx3L05WcWZvb2svaDVpTlVDVDl5c3dTSnIvMGZJbWlWbm9UbElvRVlHMmVDTmVqWjVjCmczOXVEM1pUcWQ5WnhXd1NMTG5JKzJrcEpuWkJQY2QxWlE4QVFxekRnWnRZUkNxYWNuNWdja1FVS1pXS1FseG8KRWZ0NmcxWEhKb3VBV0FadzdoRXRrMHY4ckcwL2VLRjd3YW14Rmk2QkZWbGJqV0JzQjRUOXJBcGJkQldUS2VDSgpIdjhmdjVSTUZTenBUM3V6VE84Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDaDVSaGJ4cUplK1ovZ2MxN2NaaEtObWRpd3UKSTJwTHAzUUJmd3ZOK1dibWFqencvN3JZaFkwZDhKWVZUSnpYU0NQV2k2VUFLeEF0WE9MRjhXSUlmOWkzOW42Ugp1S09CR1cxNEZ6ekd5UkppRDNxYUcvSlR2RVcrU0xod2w2OE5kcjVMSFNuYnVnQXFxMzFhYmNReTZabDl2NUE4CkpLQzk3TGovU244cmo3b3BLeTRXM29xN05DUXNBYjB6aDRJbGxSRjZVdlNuSnlTZnNnN3hkWEhIcHhZREh0T1MKWGNPdTV5U1VJWlRnRmU5UmZlVVpsR1o1eG4wY2tNbFE3cVcyV3gxcTBPVld3NXVzNE50a0dxS3JIRzRUbjFYNwp1d28vWXl0bjVzRHhyRHYxL29paTZBWk9Dc1RQcmU0b0Qzd3o0bm1WekNWSmNncnFINFEyNGhUOFdOZz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
|
||||
"tls.key": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBdmNxRmkvYUFoRFVQRE9IWnhZeUozNmg4Q2U4c2g5WFplL3JXOU1zYW10UWo1ZFNvCkhqK0MxL3BmcFEyZlF0NjIzYWQzeW5qREx1SER2ZUZUK0NCMitaWW0rb0d0S056Y2taL0w2U1BycDhwZGY0OU8KVnN2dEJ2Q0pHOXhLb2hjaGFWV1VjZkVRSmFzUVB5TUs2WEQ4MVdwK2lpVCtIbUkxUUpQM0t6Qkltdi9SOGlhSgpXZWhPVWlnUmdiWjRJMTZObmx5RGYyNFBkbE9wMzFuRmJCSXN1Y2o3YVNrbWRrRTl4M1ZsRHdCQ3JNT0JtMWhFCktwcHlmbUJ5UkJRcGxZcENYR2dSKzNxRFZjY21pNEJZQm5EdUVTMlRTL3lzYlQ5NG9YdkJxYkVXTG9FVldWdU4KWUd3SGhQMnNDbHQwRlpNcDRJa2UveCsvbEV3VkxPbFBlN05NN3dJREFRQUJBb0lCQUZDMXRVRW1ITlVjTTBCSgpNM0Q5S1F6Qis2M0YxbXdWbHgxUU9PVjFFZVZSM2NvNU94MVI2UFNyOXN5Y0ZHUTlqZ3FJMHpwNVRKZTlUcDZMCkdraGtsZlBoMU1Xbks5bzZ3bG56V0tYV3JycDJKbmkrbXBQeXVPUEFtcTRNYW5pdjJYZVArMGJST3dxcHlvanYKQUE3eUM3TStUSDIyNlpKR05WczNFVjkrY3dIbWwweXV6QmZJSm4vcnYvdzJnK1dSS00vTUMwUzdrMmQ4YlJsQQpOeWNLVkdBR0JoS1RsdGpvVllPZWg2YUhFcFNqSzh6ZmFlUGpvNWRZSnZvVklsaTYwWUNnY0pPVS84alhUK05wCjFGbTd0UnZBdGozcFVwMFNxZGFmMlJVemg5amZKcDJWRkNIdVNKNlRQcUFyT3lRb2p0TWNUSEYwVGlXN3hySFAKeE9DUklBRUNnWUVBd0dCUFU3dmR0aE1KQmcrT1JVb0dRUWFJdFRlSnZRd0lxSnZiS0Qyb3NwNGpoUzFkR1pCdwpXMzBHS0VjL2dkOEpOdE9xOUJCbk1pY1BGN2hrdHV5K2JTUHY0MVhQdWQ2N3JTU083VHN3MjBDMTBnRlJxMDZCCnpJSldGQVVxSzNJa3ZWYzNWRG10U0xTRG94NFFaL0JkcWFNbFE1eTVKQ3NDNWtUaG1rWkZsTzhDZ1lFQS9JOVgKWUhpNlJpb01KRTFmcU9ISkw0RERqbGV6bWN1UnJEN2ZFNUluS2J0SloySmhHWU9YL0MwS1huSFRPV1RDRHh4TgpGQnZwdkQ2WHY1bzNQaEI5WjZrMmZxdko0R1M4dXJrRy9LVTR4Y0MrYmFrKzlhdmE4b2FpU3FHMTZ6RDlOSDJQCmpKNjBOcmJMbDFKMHBVOWZpd3VGVlVLSjRoRFpPZk45UnFZZHlBRUNnWUFWd284V2hKaUdnTTZ6ZmN6MDczT1gKcFZxUFRQSHFqVkxwWjMrNXBJZlJkR3ZHSTZSMVFNNUV1dmFZVmI3TVBPTTQ3V1pYNXdjVk9DL1AyZzZpVmxNUAoyMUhHSUMyMzg0YTlCZmFZeE9vNDBxLytTaUhudzZDUTlta3dLSWxsa3Fxdk5BOVJHcGtNTVViMmkyOEZvcjJsCmM0dkNneGE2RFpkdFhuczZUUnFQeHdLQmdDZlk1Y3hPdi9UNkJWaGs3TWJVZU0ySjMxREIvWkF5VWhWL0Jlc3MKa0FsQmgxOU1ZazJJT1o2TDdLcmlBcFYzbERhV0hJTWp0RWtEQnlZdnlxOThJbzBNWVpDeXdmTXBjYTEwSytvSQpsMkI3L0krSXVHcENaeFVFc081ZGZUcFNUR0RQdnFwTkQ5bmlGVlVXcVZpN29UTnE2ZXA5eVF0bDVTQURqcXhxCjRTQUJBb0dBSW0waFVnMXd0Y1M0NmNHTHk2UElrUE01dG9jVFNnaHR6NHZGc3VrL2k0UUE5R0JvQk8yZ0g2dHkKK2tKSG1lYVh0MmRtZ3lTcDBRQVdpdDVVbGNlRXVtQjBOWG5BZEpaUXhlR1NGU3lZa0RXaHdYZDh3RGNlS28vMQpMZkNVNkRrOElOL1NzcHBWVVdYUTJybE9SdnhsckhlQ2lvOG8wa1M5WWl1NTVXTVlnNGc9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg=="
|
||||
}`,
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var logBuf bytes.Buffer
|
||||
testLog := log.New(&logBuf, "", 0)
|
||||
exited := "exiting via fatal"
|
||||
fail = func(format string, v ...interface{}) {
|
||||
testLog.Printf(format, v...)
|
||||
panic(exited)
|
||||
}
|
||||
|
||||
var sawSleep time.Duration
|
||||
sleep = func(d time.Duration) { sawSleep = d }
|
||||
|
||||
var sawOutput bytes.Buffer
|
||||
out = &sawOutput
|
||||
if tt.failOutput {
|
||||
out = &errWriter{}
|
||||
}
|
||||
|
||||
os.Args = tt.args
|
||||
getenv = func(key string) string { return tt.env[key] }
|
||||
if tt.wantFail {
|
||||
require.PanicsWithValue(t, exited, main)
|
||||
} else {
|
||||
require.NotPanics(t, main)
|
||||
}
|
||||
require.Equal(t, tt.wantSleep.String(), sawSleep.String())
|
||||
require.Equal(t, tt.wantLog, logBuf.String())
|
||||
if tt.wantOutJSON == "" {
|
||||
require.Empty(t, sawOutput.String())
|
||||
} else {
|
||||
require.JSONEq(t, tt.wantOutJSON, sawOutput.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
17
cmd/pinniped-concierge-kube-cert-agent/testdata/test.crt
vendored
Normal file
17
cmd/pinniped-concierge-kube-cert-agent/testdata/test.crt
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
|
||||
cm5ldGVzMB4XDTIwMDcyNTIxMDQxOFoXDTMwMDcyMzIxMDQxOFowFTETMBEGA1UE
|
||||
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3K
|
||||
hYv2gIQ1Dwzh2cWMid+ofAnvLIfV2Xv61vTLGprUI+XUqB4/gtf6X6UNn0Lett2n
|
||||
d8p4wy7hw73hU/ggdvmWJvqBrSjc3JGfy+kj66fKXX+PTlbL7QbwiRvcSqIXIWlV
|
||||
lHHxECWrED8jCulw/NVqfook/h5iNUCT9yswSJr/0fImiVnoTlIoEYG2eCNejZ5c
|
||||
g39uD3ZTqd9ZxWwSLLnI+2kpJnZBPcd1ZQ8AQqzDgZtYRCqacn5gckQUKZWKQlxo
|
||||
Eft6g1XHJouAWAZw7hEtk0v8rG0/eKF7wamxFi6BFVlbjWBsB4T9rApbdBWTKeCJ
|
||||
Hv8fv5RMFSzpT3uzTO8CAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACh5RhbxqJe+Z/gc17cZhKNmdiwu
|
||||
I2pLp3QBfwvN+Wbmajzw/7rYhY0d8JYVTJzXSCPWi6UAKxAtXOLF8WIIf9i39n6R
|
||||
uKOBGW14FzzGyRJiD3qaG/JTvEW+SLhwl68Ndr5LHSnbugAqq31abcQy6Zl9v5A8
|
||||
JKC97Lj/Sn8rj7opKy4W3oq7NCQsAb0zh4IllRF6UvSnJySfsg7xdXHHpxYDHtOS
|
||||
XcOu5ySUIZTgFe9RfeUZlGZ5xn0ckMlQ7qW2Wx1q0OVWw5us4NtkGqKrHG4Tn1X7
|
||||
uwo/Yytn5sDxrDv1/oii6AZOCsTPre4oD3wz4nmVzCVJcgrqH4Q24hT8WNg=
|
||||
-----END CERTIFICATE-----
|
||||
27
cmd/pinniped-concierge-kube-cert-agent/testdata/test.key
vendored
Normal file
27
cmd/pinniped-concierge-kube-cert-agent/testdata/test.key
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAvcqFi/aAhDUPDOHZxYyJ36h8Ce8sh9XZe/rW9MsamtQj5dSo
|
||||
Hj+C1/pfpQ2fQt623ad3ynjDLuHDveFT+CB2+ZYm+oGtKNzckZ/L6SPrp8pdf49O
|
||||
VsvtBvCJG9xKohchaVWUcfEQJasQPyMK6XD81Wp+iiT+HmI1QJP3KzBImv/R8iaJ
|
||||
WehOUigRgbZ4I16NnlyDf24PdlOp31nFbBIsucj7aSkmdkE9x3VlDwBCrMOBm1hE
|
||||
KppyfmByRBQplYpCXGgR+3qDVccmi4BYBnDuES2TS/ysbT94oXvBqbEWLoEVWVuN
|
||||
YGwHhP2sClt0FZMp4Ike/x+/lEwVLOlPe7NM7wIDAQABAoIBAFC1tUEmHNUcM0BJ
|
||||
M3D9KQzB+63F1mwVlx1QOOV1EeVR3co5Ox1R6PSr9sycFGQ9jgqI0zp5TJe9Tp6L
|
||||
GkhklfPh1MWnK9o6wlnzWKXWrrp2Jni+mpPyuOPAmq4Maniv2XeP+0bROwqpyojv
|
||||
AA7yC7M+TH226ZJGNVs3EV9+cwHml0yuzBfIJn/rv/w2g+WRKM/MC0S7k2d8bRlA
|
||||
NycKVGAGBhKTltjoVYOeh6aHEpSjK8zfaePjo5dYJvoVIli60YCgcJOU/8jXT+Np
|
||||
1Fm7tRvAtj3pUp0Sqdaf2RUzh9jfJp2VFCHuSJ6TPqArOyQojtMcTHF0TiW7xrHP
|
||||
xOCRIAECgYEAwGBPU7vdthMJBg+ORUoGQQaItTeJvQwIqJvbKD2osp4jhS1dGZBw
|
||||
W30GKEc/gd8JNtOq9BBnMicPF7hktuy+bSPv41XPud67rSSO7Tsw20C10gFRq06B
|
||||
zIJWFAUqK3IkvVc3VDmtSLSDox4QZ/BdqaMlQ5y5JCsC5kThmkZFlO8CgYEA/I9X
|
||||
YHi6RioMJE1fqOHJL4DDjlezmcuRrD7fE5InKbtJZ2JhGYOX/C0KXnHTOWTCDxxN
|
||||
FBvpvD6Xv5o3PhB9Z6k2fqvJ4GS8urkG/KU4xcC+bak+9ava8oaiSqG16zD9NH2P
|
||||
jJ60NrbLl1J0pU9fiwuFVUKJ4hDZOfN9RqYdyAECgYAVwo8WhJiGgM6zfcz073OX
|
||||
pVqPTPHqjVLpZ3+5pIfRdGvGI6R1QM5EuvaYVb7MPOM47WZX5wcVOC/P2g6iVlMP
|
||||
21HGIC2384a9BfaYxOo40q/+SiHnw6CQ9mkwKIllkqqvNA9RGpkMMUb2i28For2l
|
||||
c4vCgxa6DZdtXns6TRqPxwKBgCfY5cxOv/T6BVhk7MbUeM2J31DB/ZAyUhV/Bess
|
||||
kAlBh19MYk2IOZ6L7KriApV3lDaWHIMjtEkDByYvyq98Io0MYZCywfMpca10K+oI
|
||||
l2B7/I+IuGpCZxUEsO5dfTpSTGDPvqpND9niFVUWqVi7oTNq6ep9yQtl5SADjqxq
|
||||
4SABAoGAIm0hUg1wtcS46cGLy6PIkPM5tocTSghtz4vFsuk/i4QA9GBoBO2gH6ty
|
||||
+kJHmeaXt2dmgySp0QAWit5UlceEumB0NXnAdJZQxeGSFSyYkDWhwXd8wDceKo/1
|
||||
LfCU6Dk8IN/SsppVUWXQ2rlORvxlrHeCio8o0kS9Yiu55WMYg4g=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/client-go/pkg/version"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/component-base/logs"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"go.pinniped.dev/internal/concierge/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logs.InitLogs()
|
||||
defer logs.FlushLogs()
|
||||
|
||||
// Dump out the time since compile (mostly useful for benchmarking our local development cycle latency).
|
||||
var timeSinceCompile time.Duration
|
||||
if buildDate, err := time.Parse(time.RFC3339, version.Get().BuildDate); err == nil {
|
||||
timeSinceCompile = time.Since(buildDate).Round(time.Second)
|
||||
}
|
||||
klog.Infof("Running %s at %#v (%s since build)", rest.DefaultKubernetesUserAgent(), version.Get(), timeSinceCompile)
|
||||
|
||||
ctx := genericapiserver.SetupSignalContext()
|
||||
|
||||
if err := server.New(ctx, os.Args[1:], os.Stdout, os.Stderr).Run(); err != nil {
|
||||
klog.Fatal(err)
|
||||
}
|
||||
}
|
||||
41
cmd/pinniped-server/main.go
Normal file
41
cmd/pinniped-server/main.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main is the combined entrypoint for all Pinniped server components.
|
||||
//
|
||||
// It dispatches to the appropriate Main() entrypoint based the name it is invoked as (os.Args[0]). In our server
|
||||
// container image, this binary is symlinked to several names such as `/usr/local/bin/pinniped-concierge`.
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
concierge "go.pinniped.dev/internal/concierge/server"
|
||||
lua "go.pinniped.dev/internal/localuserauthenticator"
|
||||
supervisor "go.pinniped.dev/internal/supervisor/server"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals // these are swapped during unit tests.
|
||||
var (
|
||||
fail = klog.Fatalf
|
||||
subcommands = map[string]func(){
|
||||
"pinniped-concierge": concierge.Main,
|
||||
"pinniped-supervisor": supervisor.Main,
|
||||
"local-user-authenticator": lua.Main,
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 0 {
|
||||
fail("missing os.Args")
|
||||
}
|
||||
binary := filepath.Base(os.Args[0])
|
||||
if subcommands[binary] == nil {
|
||||
fail("must be invoked as one of %v, not %q", sets.StringKeySet(subcommands).List(), binary)
|
||||
}
|
||||
subcommands[binary]()
|
||||
}
|
||||
72
cmd/pinniped-server/main_test.go
Normal file
72
cmd/pinniped-server/main_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEntrypoint(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
args []string
|
||||
wantOutput string
|
||||
wantFail bool
|
||||
wantArgs []string
|
||||
}{
|
||||
{
|
||||
name: "missing args",
|
||||
args: []string{},
|
||||
wantOutput: "missing os.Args\n",
|
||||
wantFail: true,
|
||||
},
|
||||
{
|
||||
name: "invalid subcommand",
|
||||
args: []string{"/path/to/invalid", "some", "args"},
|
||||
wantOutput: "must be invoked as one of [another-test-binary valid-test-binary], not \"invalid\"\n",
|
||||
wantFail: true,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
args: []string{"/path/to/valid-test-binary", "foo", "bar"},
|
||||
wantArgs: []string{"/path/to/valid-test-binary", "foo", "bar"},
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var logBuf bytes.Buffer
|
||||
testLog := log.New(&logBuf, "", 0)
|
||||
exited := "exiting via fatal"
|
||||
fail = func(format string, v ...interface{}) {
|
||||
testLog.Printf(format, v...)
|
||||
panic(exited)
|
||||
}
|
||||
|
||||
// Make a test command that records os.Args when it's invoked.
|
||||
var gotArgs []string
|
||||
subcommands = map[string]func(){
|
||||
"valid-test-binary": func() { gotArgs = os.Args },
|
||||
"another-test-binary": func() {},
|
||||
}
|
||||
|
||||
os.Args = tt.args
|
||||
if tt.wantFail {
|
||||
require.PanicsWithValue(t, exited, main)
|
||||
} else {
|
||||
require.NotPanics(t, main)
|
||||
}
|
||||
if tt.wantArgs != nil {
|
||||
require.Equal(t, tt.wantArgs, gotArgs)
|
||||
}
|
||||
if tt.wantOutput != "" {
|
||||
require.Equal(t, tt.wantOutput, logBuf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
106
cmd/pinniped/cmd/flag_types.go
Normal file
106
cmd/pinniped/cmd/flag_types.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
configv1alpha1 "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.
|
||||
// this is meant to be a valid flag.Value implementation.
|
||||
type conciergeModeFlag int
|
||||
|
||||
var _ flag.Value = new(conciergeModeFlag)
|
||||
|
||||
const (
|
||||
modeUnknown conciergeModeFlag = iota
|
||||
modeTokenCredentialRequestAPI
|
||||
modeImpersonationProxy
|
||||
)
|
||||
|
||||
func (f *conciergeModeFlag) String() string {
|
||||
switch *f {
|
||||
case modeImpersonationProxy:
|
||||
return "ImpersonationProxy"
|
||||
case modeTokenCredentialRequestAPI:
|
||||
return "TokenCredentialRequestAPI"
|
||||
case modeUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
return "TokenCredentialRequestAPI"
|
||||
}
|
||||
}
|
||||
|
||||
func (f *conciergeModeFlag) Set(s string) error {
|
||||
if strings.EqualFold(s, "") {
|
||||
*f = modeUnknown
|
||||
return nil
|
||||
}
|
||||
if strings.EqualFold(s, "TokenCredentialRequestAPI") {
|
||||
*f = modeTokenCredentialRequestAPI
|
||||
return nil
|
||||
}
|
||||
if strings.EqualFold(s, "ImpersonationProxy") {
|
||||
*f = modeImpersonationProxy
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid mode %q, valid modes are TokenCredentialRequestAPI and ImpersonationProxy", s)
|
||||
}
|
||||
|
||||
func (f *conciergeModeFlag) Type() string {
|
||||
return "mode"
|
||||
}
|
||||
|
||||
// MatchesFrontend returns true iff the flag matches the type of the provided frontend.
|
||||
func (f *conciergeModeFlag) MatchesFrontend(frontend *configv1alpha1.CredentialIssuerFrontend) bool {
|
||||
switch *f {
|
||||
case modeImpersonationProxy:
|
||||
return frontend.Type == configv1alpha1.ImpersonationProxyFrontendType
|
||||
case modeTokenCredentialRequestAPI:
|
||||
return frontend.Type == configv1alpha1.TokenCredentialRequestAPIFrontendType
|
||||
case modeUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// caBundlePathsVar represents a list of CA bundle paths, which load from disk when the flag is populated.
|
||||
type caBundleFlag []byte
|
||||
|
||||
var _ pflag.Value = new(caBundleFlag)
|
||||
|
||||
func (f *caBundleFlag) String() string {
|
||||
return string(*f)
|
||||
}
|
||||
|
||||
func (f *caBundleFlag) Set(path string) error {
|
||||
pem, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read CA bundle path: %w", err)
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(pem) {
|
||||
return fmt.Errorf("failed to load any CA certificates from %q", path)
|
||||
}
|
||||
if len(*f) == 0 {
|
||||
*f = pem
|
||||
return nil
|
||||
}
|
||||
*f = bytes.Join([][]byte{*f, pem}, []byte("\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *caBundleFlag) Type() string {
|
||||
return "path"
|
||||
}
|
||||
73
cmd/pinniped/cmd/flag_types_test.go
Normal file
73
cmd/pinniped/cmd/flag_types_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
"go.pinniped.dev/internal/certauthority"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
)
|
||||
|
||||
func TestConciergeModeFlag(t *testing.T) {
|
||||
var f conciergeModeFlag
|
||||
require.Equal(t, "mode", f.Type())
|
||||
require.Equal(t, modeUnknown, f)
|
||||
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.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.NoError(t, f.Set("tokencredentialrequestapi"))
|
||||
require.Equal(t, modeTokenCredentialRequestAPI, f)
|
||||
require.Equal(t, "TokenCredentialRequestAPI", f.String())
|
||||
|
||||
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.NoError(t, f.Set("impersonationproxy"))
|
||||
require.Equal(t, modeImpersonationProxy, f)
|
||||
require.Equal(t, "ImpersonationProxy", f.String())
|
||||
}
|
||||
|
||||
func TestCABundleFlag(t *testing.T) {
|
||||
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||
require.NoError(t, err)
|
||||
tmpdir := testutil.TempDir(t)
|
||||
emptyFilePath := filepath.Join(tmpdir, "empty")
|
||||
require.NoError(t, ioutil.WriteFile(emptyFilePath, []byte{}, 0600))
|
||||
|
||||
testCAPath := filepath.Join(tmpdir, "testca.pem")
|
||||
require.NoError(t, ioutil.WriteFile(testCAPath, testCA.Bundle(), 0600))
|
||||
|
||||
f := caBundleFlag{}
|
||||
require.Equal(t, "path", f.Type())
|
||||
require.Equal(t, "", f.String())
|
||||
require.EqualError(t, f.Set("./does/not/exist"), "could not read CA bundle path: open ./does/not/exist: no such file or directory")
|
||||
require.EqualError(t, f.Set(emptyFilePath), fmt.Sprintf("failed to load any CA certificates from %q", emptyFilePath))
|
||||
|
||||
require.NoError(t, f.Set(testCAPath))
|
||||
require.Equal(t, 1, bytes.Count(f, []byte("BEGIN CERTIFICATE")))
|
||||
|
||||
require.NoError(t, f.Set(testCAPath))
|
||||
require.Equal(t, 2, bytes.Count(f, []byte("BEGIN CERTIFICATE")))
|
||||
}
|
||||
114
cmd/pinniped/cmd/generate_markdown_help.go
Normal file
114
cmd/pinniped/cmd/generate_markdown_help.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/cobra/doc"
|
||||
)
|
||||
|
||||
//nolint: gochecknoinits
|
||||
func init() {
|
||||
rootCmd.AddCommand(generateMarkdownHelpCommand())
|
||||
}
|
||||
|
||||
func generateMarkdownHelpCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Args: cobra.NoArgs,
|
||||
Use: "generate-markdown-help",
|
||||
Short: "Generate markdown help for the current set of non-hidden CLI commands",
|
||||
SilenceUsage: true,
|
||||
Hidden: true,
|
||||
RunE: runGenerateMarkdownHelp,
|
||||
}
|
||||
}
|
||||
|
||||
func runGenerateMarkdownHelp(cmd *cobra.Command, _ []string) error {
|
||||
var generated bytes.Buffer
|
||||
if err := generate(&generated); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := write(cmd.OutOrStdout(), &generated, "###### Auto generated by spf13/cobra"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generate(w io.Writer) error {
|
||||
if err := generateHeader(w); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := generateCommand(w, rootCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateHeader(w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, `---
|
||||
title: Command-Line Options Reference
|
||||
description: Reference for the `+"`pinniped`"+` command-line tool
|
||||
cascade:
|
||||
layout: docs
|
||||
menu:
|
||||
docs:
|
||||
name: Command-Line Options
|
||||
weight: 30
|
||||
parent: reference
|
||||
---
|
||||
|
||||
`)
|
||||
return err
|
||||
}
|
||||
|
||||
func generateCommand(w io.Writer, command *cobra.Command) error {
|
||||
for _, command := range command.Commands() {
|
||||
// if this node is hidden, don't traverse it or its descendents
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
// generate children
|
||||
if err := generateCommand(w, command); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// generate self, but only if we are a command that people would run to do something interesting
|
||||
if command.Run != nil || command.RunE != nil {
|
||||
if err := doc.GenMarkdownCustom(command, w, func(_ string) string { return "" }); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func write(w io.Writer, r io.Reader, unwantedPrefixes ...string) error {
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
if !containsPrefix(line, unwantedPrefixes) {
|
||||
if _, err := fmt.Fprintln(w, line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.Err()
|
||||
}
|
||||
|
||||
func containsPrefix(s string, prefixes []string) bool {
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
43
cmd/pinniped/cmd/kube_util.go
Normal file
43
cmd/pinniped/cmd/kube_util.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
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
|
||||
// clientConfig and the apiGroupSuffix with which the API is running.
|
||||
type getConciergeClientsetFunc func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error)
|
||||
|
||||
// getRealConciergeClientset returns a real implementation of a conciergeclientset.Interface.
|
||||
func getRealConciergeClientset(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
|
||||
restConfig, err := clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := kubeclient.New(
|
||||
kubeclient.WithConfig(restConfig),
|
||||
kubeclient.WithMiddleware(groupsuffix.New(apiGroupSuffix)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.PinnipedConcierge, nil
|
||||
}
|
||||
|
||||
// newClientConfig returns a clientcmd.ClientConfig given an optional kubeconfig path override and
|
||||
// an optional context override.
|
||||
func newClientConfig(kubeconfigPathOverride string, currentContextName string) clientcmd.ClientConfig {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.ExplicitPath = kubeconfigPathOverride
|
||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{
|
||||
CurrentContext: currentContextName,
|
||||
})
|
||||
return clientConfig
|
||||
}
|
||||
@@ -4,54 +4,50 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/go-logr/stdr"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
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/client-go/transport"
|
||||
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go
|
||||
|
||||
conciergev1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/authentication/v1alpha1"
|
||||
conciergeclientset "go.pinniped.dev/generated/1.20/client/concierge/clientset/versioned"
|
||||
conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
idpdiscoveryv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
||||
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
"go.pinniped.dev/internal/kubeclient"
|
||||
)
|
||||
|
||||
type kubeconfigDeps struct {
|
||||
getPathToSelf func() (string, error)
|
||||
getClientset func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error)
|
||||
getClientset getConciergeClientsetFunc
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
func kubeconfigRealDeps() kubeconfigDeps {
|
||||
return kubeconfigDeps{
|
||||
getPathToSelf: os.Executable,
|
||||
getClientset: func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
|
||||
restConfig, err := clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := kubeclient.New(
|
||||
kubeclient.WithConfig(restConfig),
|
||||
kubeclient.WithMiddleware(groupsuffix.New(apiGroupSuffix)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.PinnipedConcierge, nil
|
||||
},
|
||||
getClientset: getRealConciergeClientset,
|
||||
log: stdr.New(log.New(os.Stderr, "", 0)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,26 +62,41 @@ type getKubeconfigOIDCParams struct {
|
||||
listenPort uint16
|
||||
scopes []string
|
||||
skipBrowser bool
|
||||
skipListen bool
|
||||
sessionCachePath string
|
||||
debugSessionCache bool
|
||||
caBundlePaths []string
|
||||
caBundle caBundleFlag
|
||||
requestAudience string
|
||||
upstreamIDPName string
|
||||
upstreamIDPType string
|
||||
upstreamIDPFlow string
|
||||
}
|
||||
|
||||
type getKubeconfigConciergeParams struct {
|
||||
disabled bool
|
||||
credentialIssuer string
|
||||
authenticatorName string
|
||||
authenticatorType string
|
||||
apiGroupSuffix string
|
||||
caBundle caBundleFlag
|
||||
endpoint string
|
||||
mode conciergeModeFlag
|
||||
skipWait bool
|
||||
}
|
||||
|
||||
type getKubeconfigParams struct {
|
||||
kubeconfigPath string
|
||||
kubeconfigContextOverride string
|
||||
skipValidate bool
|
||||
timeout time.Duration
|
||||
outputPath string
|
||||
staticToken string
|
||||
staticTokenEnvName string
|
||||
oidc getKubeconfigOIDCParams
|
||||
concierge getKubeconfigConciergeParams
|
||||
generatedNameSuffix string
|
||||
credentialCachePath string
|
||||
credentialCachePathSet bool
|
||||
}
|
||||
|
||||
func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
||||
@@ -104,56 +115,69 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
||||
f.StringVar(&flags.staticToken, "static-token", "", "Instead of doing an OIDC-based login, specify a static token")
|
||||
f.StringVar(&flags.staticTokenEnvName, "static-token-env", "", "Instead of doing an OIDC-based login, read a static token from the environment")
|
||||
|
||||
f.BoolVar(&flags.concierge.disabled, "no-concierge", false, "Generate a configuration which does not use the concierge, but sends the credential to the cluster directly")
|
||||
f.StringVar(&namespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the concierge was installed")
|
||||
f.BoolVar(&flags.concierge.disabled, "no-concierge", false, "Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly")
|
||||
f.StringVar(&namespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the Concierge was installed")
|
||||
f.StringVar(&flags.concierge.credentialIssuer, "concierge-credential-issuer", "", "Concierge CredentialIssuer object to use for autodiscovery (default: autodiscover)")
|
||||
f.StringVar(&flags.concierge.authenticatorType, "concierge-authenticator-type", "", "Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)")
|
||||
f.StringVar(&flags.concierge.authenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name (default: autodiscover)")
|
||||
f.StringVar(&flags.concierge.apiGroupSuffix, "concierge-api-group-suffix", "pinniped.dev", "Concierge API group suffix")
|
||||
f.StringVar(&flags.concierge.apiGroupSuffix, "concierge-api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix")
|
||||
f.BoolVar(&flags.concierge.skipWait, "concierge-skip-wait", false, "Skip waiting for any pending Concierge strategies to become ready (default: false)")
|
||||
|
||||
f.Var(&flags.concierge.caBundle, "concierge-ca-bundle", "Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the Concierge")
|
||||
f.StringVar(&flags.concierge.endpoint, "concierge-endpoint", "", "API base for the Concierge endpoint")
|
||||
f.Var(&flags.concierge.mode, "concierge-mode", "Concierge mode of operation")
|
||||
|
||||
f.StringVar(&flags.oidc.issuer, "oidc-issuer", "", "OpenID Connect issuer URL (default: autodiscover)")
|
||||
f.StringVar(&flags.oidc.clientID, "oidc-client-id", "pinniped-cli", "OpenID Connect client ID (default: autodiscover)")
|
||||
f.Uint16Var(&flags.oidc.listenPort, "oidc-listen-port", 0, "TCP port for localhost listener (authorization code flow only)")
|
||||
f.StringSliceVar(&flags.oidc.scopes, "oidc-scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped:request-audience"}, "OpenID Connect scopes to request during login")
|
||||
f.BoolVar(&flags.oidc.skipBrowser, "oidc-skip-browser", false, "During OpenID Connect login, skip opening the browser (just print the URL)")
|
||||
f.BoolVar(&flags.oidc.skipListen, "oidc-skip-listen", false, "During OpenID Connect login, skip starting a localhost callback listener (manual copy/paste flow only)")
|
||||
f.StringVar(&flags.oidc.sessionCachePath, "oidc-session-cache", "", "Path to OpenID Connect session cache file")
|
||||
f.StringSliceVar(&flags.oidc.caBundlePaths, "oidc-ca-bundle", nil, "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)")
|
||||
f.Var(&flags.oidc.caBundle, "oidc-ca-bundle", "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)")
|
||||
f.BoolVar(&flags.oidc.debugSessionCache, "oidc-debug-session-cache", false, "Print debug logs related to the OpenID Connect session cache")
|
||||
f.StringVar(&flags.oidc.requestAudience, "oidc-request-audience", "", "Request a token with an alternate audience using RFC8693 token exchange")
|
||||
f.StringVar(&flags.oidc.upstreamIDPName, "upstream-identity-provider-name", "", "The name of the upstream identity provider used during login with a Supervisor")
|
||||
f.StringVar(&flags.oidc.upstreamIDPType, "upstream-identity-provider-type", "", fmt.Sprintf("The type of the upstream identity provider used during login with a Supervisor (e.g. '%s', '%s', '%s')", idpdiscoveryv1alpha1.IDPTypeOIDC, idpdiscoveryv1alpha1.IDPTypeLDAP, idpdiscoveryv1alpha1.IDPTypeActiveDirectory))
|
||||
f.StringVar(&flags.oidc.upstreamIDPFlow, "upstream-identity-provider-flow", "", fmt.Sprintf("The type of client flow to use with the upstream identity provider during login with a Supervisor (e.g. '%s', '%s')", idpdiscoveryv1alpha1.IDPFlowCLIPassword, idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode))
|
||||
f.StringVar(&flags.kubeconfigPath, "kubeconfig", os.Getenv("KUBECONFIG"), "Path to kubeconfig file")
|
||||
f.StringVar(&flags.kubeconfigContextOverride, "kubeconfig-context", "", "Kubeconfig context name (default: current active context)")
|
||||
|
||||
f.BoolVar(&flags.skipValidate, "skip-validation", false, "Skip final validation of the kubeconfig (default: false)")
|
||||
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")
|
||||
mustMarkHidden(cmd, "oidc-debug-session-cache")
|
||||
|
||||
// --oidc-skip-listen is mainly needed for testing. We'll leave it hidden until we have a non-testing use case.
|
||||
mustMarkHidden(cmd, "oidc-skip-listen")
|
||||
|
||||
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
|
||||
mustMarkHidden(cmd, "concierge-namespace")
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error { return runGetKubeconfig(cmd.OutOrStdout(), deps, flags) }
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
if flags.outputPath != "" {
|
||||
out, err := os.Create(flags.outputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open output file: %w", err)
|
||||
}
|
||||
defer func() { _ = out.Close() }()
|
||||
cmd.SetOut(out)
|
||||
}
|
||||
flags.credentialCachePathSet = cmd.Flags().Changed("credential-cache")
|
||||
return runGetKubeconfig(cmd.Context(), cmd.OutOrStdout(), deps, flags)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigParams) error {
|
||||
func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, flags getKubeconfigParams) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, flags.timeout)
|
||||
defer cancel()
|
||||
|
||||
// Validate api group suffix and immediately return an error if it is invalid.
|
||||
if err := groupsuffix.Validate(flags.concierge.apiGroupSuffix); err != nil {
|
||||
return fmt.Errorf("invalid api group suffix: %w", err)
|
||||
}
|
||||
|
||||
execConfig := clientcmdapi.ExecConfig{
|
||||
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
|
||||
Args: []string{},
|
||||
Env: []clientcmdapi.ExecEnvVar{},
|
||||
}
|
||||
|
||||
var err error
|
||||
execConfig.Command, err = deps.getPathToSelf()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine the Pinniped executable path: %w", err)
|
||||
}
|
||||
execConfig.ProvideClusterInfo = true
|
||||
|
||||
oidcCABundle, err := loadCABundlePaths(flags.oidc.caBundlePaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read --oidc-ca-bundle: %w", err)
|
||||
return fmt.Errorf("invalid API group suffix: %w", err)
|
||||
}
|
||||
|
||||
clientConfig := newClientConfig(flags.kubeconfigPath, flags.kubeconfigContextOverride)
|
||||
@@ -161,33 +185,107 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load --kubeconfig: %w", err)
|
||||
}
|
||||
cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, flags.kubeconfigContextOverride)
|
||||
currentKubeconfigNames, err := getCurrentContext(currentKubeConfig, flags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
|
||||
}
|
||||
cluster := currentKubeConfig.Clusters[currentKubeconfigNames.ClusterName]
|
||||
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not configure Kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
// Generate the new context/cluster/user names by appending the --generated-name-suffix to the original values.
|
||||
newKubeconfigNames := &kubeconfigNames{
|
||||
ContextName: currentKubeconfigNames.ContextName + flags.generatedNameSuffix,
|
||||
UserName: currentKubeconfigNames.UserName + flags.generatedNameSuffix,
|
||||
ClusterName: currentKubeconfigNames.ClusterName + flags.generatedNameSuffix,
|
||||
}
|
||||
|
||||
if !flags.concierge.disabled {
|
||||
credentialIssuer, err := waitForCredentialIssuer(ctx, clientset, flags, deps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authenticator, err := lookupAuthenticator(
|
||||
clientset,
|
||||
flags.concierge.authenticatorType,
|
||||
flags.concierge.authenticatorName,
|
||||
deps.log,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := configureConcierge(authenticator, &flags, cluster, &oidcCABundle, &execConfig); err != nil {
|
||||
if err := discoverConciergeParams(credentialIssuer, &flags, cluster, deps.log); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := discoverAuthenticatorParams(authenticator, &flags, deps.log); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Point kubectl at the concierge endpoint.
|
||||
cluster.Server = flags.concierge.endpoint
|
||||
cluster.CertificateAuthorityData = flags.concierge.caBundle
|
||||
}
|
||||
|
||||
// If there is an issuer, and if any upstream IDP flags are not already set, then try to discover Supervisor upstream IDP details.
|
||||
// When all the upstream IDP flags are set by the user, then skip discovery and don't validate their input. Maybe they know something
|
||||
// that we can't know, like the name of an IDP that they are going to define in the future.
|
||||
if len(flags.oidc.issuer) > 0 && (flags.oidc.upstreamIDPType == "" || flags.oidc.upstreamIDPName == "" || flags.oidc.upstreamIDPFlow == "") {
|
||||
if err := discoverSupervisorUpstreamIDP(ctx, &flags); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
execConfig, err := newExecConfig(deps, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubeconfig := newExecKubeconfig(cluster, execConfig, newKubeconfigNames)
|
||||
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeConfigAsYAML(out, kubeconfig)
|
||||
}
|
||||
|
||||
func newExecConfig(deps kubeconfigDeps, flags getKubeconfigParams) (*clientcmdapi.ExecConfig, error) {
|
||||
execConfig := &clientcmdapi.ExecConfig{
|
||||
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
|
||||
Args: []string{},
|
||||
Env: []clientcmdapi.ExecEnvVar{},
|
||||
ProvideClusterInfo: true,
|
||||
}
|
||||
|
||||
var err error
|
||||
execConfig.Command, err = deps.getPathToSelf()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine the Pinniped executable path: %w", err)
|
||||
}
|
||||
|
||||
if !flags.concierge.disabled {
|
||||
// Append the flags to configure the Concierge credential exchange at runtime.
|
||||
execConfig.Args = append(execConfig.Args,
|
||||
"--enable-concierge",
|
||||
"--concierge-api-group-suffix="+flags.concierge.apiGroupSuffix,
|
||||
"--concierge-authenticator-name="+flags.concierge.authenticatorName,
|
||||
"--concierge-authenticator-type="+flags.concierge.authenticatorType,
|
||||
"--concierge-endpoint="+flags.concierge.endpoint,
|
||||
"--concierge-ca-bundle-data="+base64.StdEncoding.EncodeToString(flags.concierge.caBundle),
|
||||
)
|
||||
}
|
||||
|
||||
// If --credential-cache is set, pass it through.
|
||||
if flags.credentialCachePathSet {
|
||||
execConfig.Args = append(execConfig.Args, "--credential-cache="+flags.credentialCachePath)
|
||||
}
|
||||
|
||||
// If one of the --static-* flags was passed, output a config that runs `pinniped login static`.
|
||||
if flags.staticToken != "" || flags.staticTokenEnvName != "" {
|
||||
if flags.staticToken != "" && flags.staticTokenEnvName != "" {
|
||||
return fmt.Errorf("only one of --static-token and --static-token-env can be specified")
|
||||
return nil, fmt.Errorf("only one of --static-token and --static-token-env can be specified")
|
||||
}
|
||||
execConfig.Args = append([]string{"login", "static"}, execConfig.Args...)
|
||||
if flags.staticToken != "" {
|
||||
@@ -196,13 +294,13 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
|
||||
if flags.staticTokenEnvName != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--token-env="+flags.staticTokenEnvName)
|
||||
}
|
||||
return writeConfigAsYAML(out, newExecKubeconfig(cluster, &execConfig))
|
||||
return execConfig, nil
|
||||
}
|
||||
|
||||
// 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...)
|
||||
if flags.oidc.issuer == "" {
|
||||
return fmt.Errorf("could not autodiscover --oidc-issuer, and none was provided")
|
||||
return nil, fmt.Errorf("could not autodiscover --oidc-issuer and none was provided")
|
||||
}
|
||||
execConfig.Args = append(execConfig.Args,
|
||||
"--issuer="+flags.oidc.issuer,
|
||||
@@ -212,11 +310,14 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
|
||||
if flags.oidc.skipBrowser {
|
||||
execConfig.Args = append(execConfig.Args, "--skip-browser")
|
||||
}
|
||||
if flags.oidc.skipListen {
|
||||
execConfig.Args = append(execConfig.Args, "--skip-listen")
|
||||
}
|
||||
if flags.oidc.listenPort != 0 {
|
||||
execConfig.Args = append(execConfig.Args, "--listen-port="+strconv.Itoa(int(flags.oidc.listenPort)))
|
||||
}
|
||||
if oidcCABundle != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--ca-bundle-data="+base64.StdEncoding.EncodeToString([]byte(oidcCABundle)))
|
||||
if len(flags.oidc.caBundle) != 0 {
|
||||
execConfig.Args = append(execConfig.Args, "--ca-bundle-data="+base64.StdEncoding.EncodeToString(flags.oidc.caBundle))
|
||||
}
|
||||
if flags.oidc.sessionCachePath != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--session-cache="+flags.oidc.sessionCachePath)
|
||||
@@ -227,15 +328,141 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
|
||||
if flags.oidc.requestAudience != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--request-audience="+flags.oidc.requestAudience)
|
||||
}
|
||||
return writeConfigAsYAML(out, newExecKubeconfig(cluster, &execConfig))
|
||||
if flags.oidc.upstreamIDPName != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--upstream-identity-provider-name="+flags.oidc.upstreamIDPName)
|
||||
}
|
||||
if flags.oidc.upstreamIDPType != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--upstream-identity-provider-type="+flags.oidc.upstreamIDPType)
|
||||
}
|
||||
if flags.oidc.upstreamIDPFlow != "" {
|
||||
execConfig.Args = append(execConfig.Args, "--upstream-identity-provider-flow="+flags.oidc.upstreamIDPFlow)
|
||||
}
|
||||
|
||||
return execConfig, nil
|
||||
}
|
||||
|
||||
func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams, v1Cluster *clientcmdapi.Cluster, oidcCABundle *string, execConfig *clientcmdapi.ExecConfig) error {
|
||||
type kubeconfigNames struct{ ContextName, UserName, ClusterName string }
|
||||
|
||||
func getCurrentContext(currentKubeConfig clientcmdapi.Config, flags getKubeconfigParams) (*kubeconfigNames, error) {
|
||||
contextName := currentKubeConfig.CurrentContext
|
||||
if flags.kubeconfigContextOverride != "" {
|
||||
contextName = flags.kubeconfigContextOverride
|
||||
}
|
||||
ctx := currentKubeConfig.Contexts[contextName]
|
||||
if ctx == nil {
|
||||
return nil, fmt.Errorf("no such context %q", contextName)
|
||||
}
|
||||
if _, exists := currentKubeConfig.Clusters[ctx.Cluster]; !exists {
|
||||
return nil, fmt.Errorf("no such cluster %q", ctx.Cluster)
|
||||
}
|
||||
if _, exists := currentKubeConfig.AuthInfos[ctx.AuthInfo]; !exists {
|
||||
return nil, fmt.Errorf("no such user %q", ctx.AuthInfo)
|
||||
}
|
||||
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) {
|
||||
credentialIssuer, err := lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !flags.concierge.skipWait {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
deadline, _ := ctx.Deadline()
|
||||
attempts := 1
|
||||
|
||||
for {
|
||||
if !hasPendingStrategy(credentialIssuer) {
|
||||
break
|
||||
}
|
||||
logStrategies(credentialIssuer, deps.log)
|
||||
deps.log.Info("waiting for CredentialIssuer pending strategies to finish",
|
||||
"attempts", attempts,
|
||||
"remaining", time.Until(deadline).Round(time.Second).String(),
|
||||
)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-ticker.C:
|
||||
credentialIssuer, err = lookupCredentialIssuer(clientset, flags.concierge.credentialIssuer, deps.log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return credentialIssuer, nil
|
||||
}
|
||||
|
||||
func discoverConciergeParams(credentialIssuer *configv1alpha1.CredentialIssuer, flags *getKubeconfigParams, v1Cluster *clientcmdapi.Cluster, log logr.Logger) error {
|
||||
// Autodiscover the --concierge-mode.
|
||||
frontend, err := getConciergeFrontend(credentialIssuer, flags.concierge.mode)
|
||||
if err != nil {
|
||||
logStrategies(credentialIssuer, log)
|
||||
return err
|
||||
}
|
||||
|
||||
// Auto-set --concierge-mode if it wasn't explicitly set.
|
||||
if flags.concierge.mode == modeUnknown {
|
||||
switch frontend.Type {
|
||||
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
|
||||
log.Info("discovered Concierge operating in TokenCredentialRequest API mode")
|
||||
flags.concierge.mode = modeTokenCredentialRequestAPI
|
||||
case configv1alpha1.ImpersonationProxyFrontendType:
|
||||
log.Info("discovered Concierge operating in impersonation proxy mode")
|
||||
flags.concierge.mode = modeImpersonationProxy
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-set --concierge-endpoint if it wasn't explicitly set.
|
||||
if flags.concierge.endpoint == "" {
|
||||
switch frontend.Type {
|
||||
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
|
||||
flags.concierge.endpoint = v1Cluster.Server
|
||||
case configv1alpha1.ImpersonationProxyFrontendType:
|
||||
flags.concierge.endpoint = frontend.ImpersonationProxyInfo.Endpoint
|
||||
}
|
||||
log.Info("discovered Concierge endpoint", "endpoint", flags.concierge.endpoint)
|
||||
}
|
||||
|
||||
// Auto-set --concierge-ca-bundle if it wasn't explicitly set..
|
||||
if len(flags.concierge.caBundle) == 0 {
|
||||
switch frontend.Type {
|
||||
case configv1alpha1.TokenCredentialRequestAPIFrontendType:
|
||||
flags.concierge.caBundle = v1Cluster.CertificateAuthorityData
|
||||
case configv1alpha1.ImpersonationProxyFrontendType:
|
||||
data, err := base64.StdEncoding.DecodeString(frontend.ImpersonationProxyInfo.CertificateAuthorityData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("autodiscovered Concierge CA bundle is invalid: %w", err)
|
||||
}
|
||||
flags.concierge.caBundle = data
|
||||
}
|
||||
log.Info("discovered Concierge certificate authority bundle", "roots", countCACerts(flags.concierge.caBundle))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logStrategies(credentialIssuer *configv1alpha1.CredentialIssuer, log logr.Logger) {
|
||||
for _, strategy := range credentialIssuer.Status.Strategies {
|
||||
log.Info("found CredentialIssuer strategy",
|
||||
"type", strategy.Type,
|
||||
"status", strategy.Status,
|
||||
"reason", strategy.Reason,
|
||||
"message", strategy.Message,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func discoverAuthenticatorParams(authenticator metav1.Object, flags *getKubeconfigParams, log logr.Logger) error {
|
||||
switch auth := authenticator.(type) {
|
||||
case *conciergev1alpha1.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 == "" {
|
||||
log.Info("discovered WebhookAuthenticator", "name", auth.Name)
|
||||
flags.concierge.authenticatorType = "webhook"
|
||||
flags.concierge.authenticatorName = auth.Name
|
||||
}
|
||||
@@ -243,71 +470,118 @@ func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams,
|
||||
// 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 == "" {
|
||||
log.Info("discovered JWTAuthenticator", "name", auth.Name)
|
||||
flags.concierge.authenticatorType = "jwt"
|
||||
flags.concierge.authenticatorName = auth.Name
|
||||
}
|
||||
|
||||
// If the --oidc-issuer flag was not set explicitly, default it to the spec.issuer field of the JWTAuthenticator.
|
||||
if flags.oidc.issuer == "" {
|
||||
log.Info("discovered OIDC issuer", "issuer", auth.Spec.Issuer)
|
||||
flags.oidc.issuer = auth.Spec.Issuer
|
||||
}
|
||||
|
||||
// If the --oidc-request-audience flag was not set explicitly, default it to the spec.audience field of the JWTAuthenticator.
|
||||
if flags.oidc.requestAudience == "" {
|
||||
log.Info("discovered OIDC audience", "audience", auth.Spec.Audience)
|
||||
flags.oidc.requestAudience = auth.Spec.Audience
|
||||
}
|
||||
|
||||
// If the --oidc-ca-bundle flags was not set explicitly, default it to the
|
||||
// spec.tls.certificateAuthorityData field of the JWTAuthenticator.
|
||||
if *oidcCABundle == "" && auth.Spec.TLS != nil && auth.Spec.TLS.CertificateAuthorityData != "" {
|
||||
if len(flags.oidc.caBundle) == 0 && auth.Spec.TLS != nil && auth.Spec.TLS.CertificateAuthorityData != "" {
|
||||
decoded, err := base64.StdEncoding.DecodeString(auth.Spec.TLS.CertificateAuthorityData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s has invalid spec.tls.certificateAuthorityData: %w", auth.Name, err)
|
||||
}
|
||||
*oidcCABundle = string(decoded)
|
||||
log.Info("discovered OIDC CA bundle", "roots", countCACerts(decoded))
|
||||
flags.oidc.caBundle = decoded
|
||||
}
|
||||
}
|
||||
|
||||
// Append the flags to configure the Concierge credential exchange at runtime.
|
||||
execConfig.Args = append(execConfig.Args,
|
||||
"--enable-concierge",
|
||||
"--concierge-api-group-suffix="+flags.concierge.apiGroupSuffix,
|
||||
"--concierge-authenticator-name="+flags.concierge.authenticatorName,
|
||||
"--concierge-authenticator-type="+flags.concierge.authenticatorType,
|
||||
"--concierge-endpoint="+v1Cluster.Server,
|
||||
"--concierge-ca-bundle-data="+base64.StdEncoding.EncodeToString(v1Cluster.CertificateAuthorityData),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadCABundlePaths(paths []string) (string, error) {
|
||||
if len(paths) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
blobs := make([][]byte, 0, len(paths))
|
||||
for _, p := range paths {
|
||||
pem, err := ioutil.ReadFile(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
func getConciergeFrontend(credentialIssuer *configv1alpha1.CredentialIssuer, mode conciergeModeFlag) (*configv1alpha1.CredentialIssuerFrontend, error) {
|
||||
for _, strategy := range credentialIssuer.Status.Strategies {
|
||||
// Skip unhealthy strategies.
|
||||
if strategy.Status != configv1alpha1.SuccessStrategyStatus {
|
||||
continue
|
||||
}
|
||||
blobs = append(blobs, pem)
|
||||
|
||||
// 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 strategy.Frontend == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip any unknown frontend types.
|
||||
switch strategy.Frontend.Type {
|
||||
case configv1alpha1.TokenCredentialRequestAPIFrontendType, configv1alpha1.ImpersonationProxyFrontendType:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
// Skip strategies that don't match --concierge-mode.
|
||||
if !mode.MatchesFrontend(strategy.Frontend) {
|
||||
continue
|
||||
}
|
||||
return strategy.Frontend, nil
|
||||
}
|
||||
return string(bytes.Join(blobs, []byte("\n"))), nil
|
||||
|
||||
if mode == modeUnknown {
|
||||
return nil, fmt.Errorf("could not autodiscover --concierge-mode")
|
||||
}
|
||||
return nil, fmt.Errorf("could not find successful Concierge strategy matching --concierge-mode=%s", mode.String())
|
||||
}
|
||||
|
||||
func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig) clientcmdapi.Config {
|
||||
const name = "pinniped"
|
||||
func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig, newNames *kubeconfigNames) clientcmdapi.Config {
|
||||
return clientcmdapi.Config{
|
||||
Kind: "Config",
|
||||
APIVersion: clientcmdapi.SchemeGroupVersion.Version,
|
||||
Clusters: map[string]*clientcmdapi.Cluster{name: cluster},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{name: {Exec: execConfig}},
|
||||
Contexts: map[string]*clientcmdapi.Context{name: {Cluster: name, AuthInfo: name}},
|
||||
CurrentContext: name,
|
||||
Clusters: map[string]*clientcmdapi.Cluster{newNames.ClusterName: cluster},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{newNames.UserName: {Exec: execConfig}},
|
||||
Contexts: map[string]*clientcmdapi.Context{newNames.ContextName: {Cluster: newNames.ClusterName, AuthInfo: newNames.UserName}},
|
||||
CurrentContext: newNames.ContextName,
|
||||
}
|
||||
}
|
||||
|
||||
func lookupAuthenticator(clientset conciergeclientset.Interface, authType, authName string) (metav1.Object, error) {
|
||||
func lookupCredentialIssuer(clientset conciergeclientset.Interface, name string, log logr.Logger) (*configv1alpha1.CredentialIssuer, error) {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancelFunc()
|
||||
|
||||
// If the name is specified, get that object.
|
||||
if name != "" {
|
||||
return clientset.ConfigV1alpha1().CredentialIssuers().Get(ctx, name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// Otherwise list all the available CredentialIssuers and hope there's just a single one
|
||||
results, err := clientset.ConfigV1alpha1().CredentialIssuers().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list CredentialIssuer objects for autodiscovery: %w", err)
|
||||
}
|
||||
if len(results.Items) == 0 {
|
||||
return nil, fmt.Errorf("no CredentialIssuers were found")
|
||||
}
|
||||
if len(results.Items) > 1 {
|
||||
return nil, fmt.Errorf("multiple CredentialIssuers were found, so the --concierge-credential-issuer flag must be specified")
|
||||
}
|
||||
|
||||
result := &results.Items[0]
|
||||
log.Info("discovered CredentialIssuer", "name", result.Name)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func lookupAuthenticator(clientset conciergeclientset.Interface, authType, authName string, log logr.Logger) (metav1.Object, error) {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancelFunc()
|
||||
|
||||
@@ -345,20 +619,17 @@ func lookupAuthenticator(clientset conciergeclientset.Interface, authType, authN
|
||||
return nil, fmt.Errorf("no authenticators were found")
|
||||
}
|
||||
if len(results) > 1 {
|
||||
for _, jwtAuth := range jwtAuths.Items {
|
||||
log.Info("found JWTAuthenticator", "name", jwtAuth.Name)
|
||||
}
|
||||
for _, webhook := range webhooks.Items {
|
||||
log.Info("found WebhookAuthenticator", "name", webhook.Name)
|
||||
}
|
||||
return nil, fmt.Errorf("multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified")
|
||||
}
|
||||
return results[0], nil
|
||||
}
|
||||
|
||||
func newClientConfig(kubeconfigPathOverride string, currentContextName string) clientcmd.ClientConfig {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.ExplicitPath = kubeconfigPathOverride
|
||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{
|
||||
CurrentContext: currentContextName,
|
||||
})
|
||||
return clientConfig
|
||||
}
|
||||
|
||||
func writeConfigAsYAML(out io.Writer, config clientcmdapi.Config) error {
|
||||
output, err := clientcmd.Write(config)
|
||||
if err != nil {
|
||||
@@ -371,14 +642,305 @@ func writeConfigAsYAML(out io.Writer, config clientcmdapi.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyCurrentClusterFromExistingKubeConfig(currentKubeConfig clientcmdapi.Config, currentContextNameOverride string) (*clientcmdapi.Cluster, error) {
|
||||
contextName := currentKubeConfig.CurrentContext
|
||||
if currentContextNameOverride != "" {
|
||||
contextName = currentContextNameOverride
|
||||
func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconfig clientcmdapi.Config, log logr.Logger) error {
|
||||
if flags.skipValidate {
|
||||
return nil
|
||||
}
|
||||
context := currentKubeConfig.Contexts[contextName]
|
||||
if context == nil {
|
||||
return nil, fmt.Errorf("no such context %q", contextName)
|
||||
|
||||
kubeContext := kubeconfig.Contexts[kubeconfig.CurrentContext]
|
||||
if kubeContext == nil {
|
||||
return fmt.Errorf("invalid kubeconfig (no context)")
|
||||
}
|
||||
cluster := kubeconfig.Clusters[kubeContext.Cluster]
|
||||
if cluster == nil {
|
||||
return fmt.Errorf("invalid kubeconfig (no cluster)")
|
||||
}
|
||||
|
||||
kubeconfigCA := x509.NewCertPool()
|
||||
if !kubeconfigCA.AppendCertsFromPEM(cluster.CertificateAuthorityData) {
|
||||
return fmt.Errorf("invalid kubeconfig (no certificateAuthorityData)")
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
RootCAs: kubeconfigCA,
|
||||
},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
},
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
pingCluster := func() error {
|
||||
reqCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, cluster.Server, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not form request to validate cluster: %w", err)
|
||||
}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
if resp.StatusCode >= 500 {
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := pingCluster()
|
||||
if err == nil {
|
||||
log.Info("validated connection to the cluster")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("could not immediately connect to the cluster but it may be initializing, will retry until timeout")
|
||||
deadline, _ := ctx.Deadline()
|
||||
attempts := 0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
attempts++
|
||||
err := pingCluster()
|
||||
if err == nil {
|
||||
log.Info("validated connection to the cluster", "attempts", attempts)
|
||||
return nil
|
||||
}
|
||||
log.Error(err, "could not connect to cluster, retrying...", "attempts", attempts, "remaining", time.Until(deadline).Round(time.Second).String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func countCACerts(pemData []byte) int {
|
||||
pool := x509.NewCertPool()
|
||||
pool.AppendCertsFromPEM(pemData)
|
||||
return len(pool.Subjects())
|
||||
}
|
||||
|
||||
func hasPendingStrategy(credentialIssuer *configv1alpha1.CredentialIssuer) bool {
|
||||
for _, strategy := range credentialIssuer.Status.Strategies {
|
||||
if strategy.Reason == configv1alpha1.PendingStrategyReason {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func discoverSupervisorUpstreamIDP(ctx context.Context, flags *getKubeconfigParams) error {
|
||||
httpClient, err := newDiscoveryHTTPClient(flags.oidc.caBundle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pinnipedIDPsEndpoint, err := discoverIDPsDiscoveryEndpointURL(ctx, flags.oidc.issuer, httpClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pinnipedIDPsEndpoint == "" {
|
||||
// The issuer is not advertising itself as a Pinniped Supervisor which supports upstream IDP discovery.
|
||||
return nil
|
||||
}
|
||||
|
||||
discoveredUpstreamIDPs, err := discoverAllAvailableSupervisorUpstreamIDPs(ctx, pinnipedIDPsEndpoint, httpClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(discoveredUpstreamIDPs) == 0 {
|
||||
// Discovered that the Supervisor does not have any upstream IDPs defined. Continue without putting one into the
|
||||
// kubeconfig. This kubeconfig will only work if the user defines one (and only one) OIDC IDP in the Supervisor
|
||||
// later and wants to use the default client flow for OIDC (browser-based auth).
|
||||
return nil
|
||||
}
|
||||
|
||||
selectedIDPName, selectedIDPType, discoveredIDPFlows, err := selectUpstreamIDPNameAndType(discoveredUpstreamIDPs, flags.oidc.upstreamIDPName, flags.oidc.upstreamIDPType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selectedIDPFlow, err := selectUpstreamIDPFlow(discoveredIDPFlows, selectedIDPName, selectedIDPType, flags.oidc.upstreamIDPFlow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flags.oidc.upstreamIDPName = selectedIDPName
|
||||
flags.oidc.upstreamIDPType = selectedIDPType.String()
|
||||
flags.oidc.upstreamIDPFlow = selectedIDPFlow.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDiscoveryHTTPClient(caBundleFlag caBundleFlag) (*http.Client, error) {
|
||||
t := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
httpClient := &http.Client{Transport: t}
|
||||
if caBundleFlag != nil {
|
||||
rootCAs := x509.NewCertPool()
|
||||
ok := rootCAs.AppendCertsFromPEM(caBundleFlag)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to fetch OIDC discovery data from issuer: could not parse CA bundle")
|
||||
}
|
||||
t.TLSClientConfig.RootCAs = rootCAs
|
||||
}
|
||||
httpClient.Transport = transport.DebugWrappers(httpClient.Transport)
|
||||
return httpClient, nil
|
||||
}
|
||||
|
||||
func discoverIDPsDiscoveryEndpointURL(ctx context.Context, issuer string, httpClient *http.Client) (string, error) {
|
||||
discoveredProvider, err := oidc.NewProvider(oidc.ClientContext(ctx, httpClient), issuer)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
|
||||
}
|
||||
|
||||
var body idpdiscoveryv1alpha1.OIDCDiscoveryResponse
|
||||
err = discoveredProvider.Claims(&body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
|
||||
}
|
||||
|
||||
return body.SupervisorDiscovery.PinnipedIDPsEndpoint, nil
|
||||
}
|
||||
|
||||
func discoverAllAvailableSupervisorUpstreamIDPs(ctx context.Context, pinnipedIDPsEndpoint string, httpClient *http.Client) ([]idpdiscoveryv1alpha1.PinnipedIDP, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, pinnipedIDPsEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while forming request to IDP discovery URL: %w", err)
|
||||
}
|
||||
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = response.Body.Close()
|
||||
}()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: unexpected http response status: %s", response.Status)
|
||||
}
|
||||
|
||||
rawBody, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: could not read response body: %w", err)
|
||||
}
|
||||
|
||||
var body idpdiscoveryv1alpha1.IDPDiscoveryResponse
|
||||
err = json.Unmarshal(rawBody, &body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch IDP discovery data from issuer: could not parse response JSON: %w", err)
|
||||
}
|
||||
|
||||
return body.PinnipedIDPs, nil
|
||||
}
|
||||
|
||||
func selectUpstreamIDPNameAndType(pinnipedIDPs []idpdiscoveryv1alpha1.PinnipedIDP, specifiedIDPName, specifiedIDPType string) (string, idpdiscoveryv1alpha1.IDPType, []idpdiscoveryv1alpha1.IDPFlow, error) {
|
||||
pinnipedIDPsString, _ := json.Marshal(pinnipedIDPs)
|
||||
var discoveredFlows []idpdiscoveryv1alpha1.IDPFlow
|
||||
switch {
|
||||
case specifiedIDPName != "" && specifiedIDPType != "":
|
||||
// The user specified both name and type, so check to see if there exists an exact match.
|
||||
for _, idp := range pinnipedIDPs {
|
||||
if idp.Name == specifiedIDPName && idp.Type.Equals(specifiedIDPType) {
|
||||
return specifiedIDPName, idp.Type, idp.Flows, nil
|
||||
}
|
||||
}
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"no Supervisor upstream identity providers with name %q of type %q were found. "+
|
||||
"Found these upstreams: %s", specifiedIDPName, specifiedIDPType, pinnipedIDPsString)
|
||||
case specifiedIDPType != "":
|
||||
// The user specified only a type, so check if there is only one of that type found.
|
||||
discoveredName := ""
|
||||
var discoveredType idpdiscoveryv1alpha1.IDPType
|
||||
for _, idp := range pinnipedIDPs {
|
||||
if idp.Type.Equals(specifiedIDPType) {
|
||||
if discoveredName != "" {
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"multiple Supervisor upstream identity providers of type %q were found, "+
|
||||
"so the --upstream-identity-provider-name flag must be specified. "+
|
||||
"Found these upstreams: %s",
|
||||
specifiedIDPType, pinnipedIDPsString)
|
||||
}
|
||||
discoveredName = idp.Name
|
||||
discoveredType = idp.Type
|
||||
discoveredFlows = idp.Flows
|
||||
}
|
||||
}
|
||||
if discoveredName == "" {
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"no Supervisor upstream identity providers of type %q were found. "+
|
||||
"Found these upstreams: %s", specifiedIDPType, pinnipedIDPsString)
|
||||
}
|
||||
return discoveredName, discoveredType, discoveredFlows, nil
|
||||
case specifiedIDPName != "":
|
||||
// The user specified only a name, so check if there is only one of that name found.
|
||||
var discoveredType idpdiscoveryv1alpha1.IDPType
|
||||
for _, idp := range pinnipedIDPs {
|
||||
if idp.Name == specifiedIDPName {
|
||||
if discoveredType != "" {
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"multiple Supervisor upstream identity providers with name %q were found, "+
|
||||
"so the --upstream-identity-provider-type flag must be specified. Found these upstreams: %s",
|
||||
specifiedIDPName, pinnipedIDPsString)
|
||||
}
|
||||
discoveredType = idp.Type
|
||||
discoveredFlows = idp.Flows
|
||||
}
|
||||
}
|
||||
if discoveredType == "" {
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"no Supervisor upstream identity providers with name %q were found. "+
|
||||
"Found these upstreams: %s", specifiedIDPName, pinnipedIDPsString)
|
||||
}
|
||||
return specifiedIDPName, discoveredType, discoveredFlows, nil
|
||||
case len(pinnipedIDPs) == 1:
|
||||
// The user did not specify any name or type, but there is only one found, so select it.
|
||||
return pinnipedIDPs[0].Name, pinnipedIDPs[0].Type, pinnipedIDPs[0].Flows, nil
|
||||
default:
|
||||
// The user did not specify any name or type, and there is more than one found.
|
||||
return "", "", nil, fmt.Errorf(
|
||||
"multiple Supervisor upstream identity providers were found, "+
|
||||
"so the --upstream-identity-provider-name/--upstream-identity-provider-type flags must be specified. "+
|
||||
"Found these upstreams: %s",
|
||||
pinnipedIDPsString)
|
||||
}
|
||||
}
|
||||
|
||||
func selectUpstreamIDPFlow(discoveredIDPFlows []idpdiscoveryv1alpha1.IDPFlow, selectedIDPName string, selectedIDPType idpdiscoveryv1alpha1.IDPType, specifiedFlow string) (idpdiscoveryv1alpha1.IDPFlow, error) {
|
||||
switch {
|
||||
case len(discoveredIDPFlows) == 0:
|
||||
// No flows listed by discovery means that we are talking to an old Supervisor from before this feature existed.
|
||||
// If the user specified a flow on the CLI flag then use it without validation, otherwise skip flow selection
|
||||
// and return empty string.
|
||||
return idpdiscoveryv1alpha1.IDPFlow(specifiedFlow), nil
|
||||
case specifiedFlow != "":
|
||||
// The user specified a flow, so validate that it is available for the selected IDP.
|
||||
for _, flow := range discoveredIDPFlows {
|
||||
if flow.Equals(specifiedFlow) {
|
||||
// Found it, so use it as specified by the user.
|
||||
return flow, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf(
|
||||
"no client flow %q for Supervisor upstream identity provider %q of type %q were found. "+
|
||||
"Found these flows: %v",
|
||||
specifiedFlow, selectedIDPName, selectedIDPType, discoveredIDPFlows)
|
||||
case len(discoveredIDPFlows) == 1:
|
||||
// The user did not specify a flow, but there is only one found, so select it.
|
||||
return discoveredIDPFlows[0], nil
|
||||
default:
|
||||
// The user did not specify a flow, and more than one was found.
|
||||
return "", fmt.Errorf(
|
||||
"multiple client flows for Supervisor upstream identity provider %q of type %q were found, "+
|
||||
"so the --upstream-identity-provider-flow flag must be specified. "+
|
||||
"Found these flows: %v",
|
||||
selectedIDPName, selectedIDPType, discoveredIDPFlows)
|
||||
}
|
||||
return currentKubeConfig.Clusters[context.Cluster], nil
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,12 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
"k8s.io/client-go/tools/auth/exec"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
@@ -20,3 +22,15 @@ var loginCmd = &cobra.Command{
|
||||
func init() {
|
||||
rootCmd.AddCommand(loginCmd)
|
||||
}
|
||||
|
||||
func loadClusterInfo() *clientauthv1beta1.Cluster {
|
||||
obj, _, err := exec.LoadExecCredentialFromEnv()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
cred, ok := obj.(*clientauthv1beta1.ExecCredential)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return cred.Spec.Cluster
|
||||
}
|
||||
|
||||
@@ -14,14 +14,20 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
"k8s.io/client-go/transport"
|
||||
"k8s.io/klog/v2/klogr"
|
||||
|
||||
idpdiscoveryv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idpdiscovery/v1alpha1"
|
||||
"go.pinniped.dev/internal/execcredcache"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/pkg/conciergeclient"
|
||||
"go.pinniped.dev/pkg/oidcclient"
|
||||
"go.pinniped.dev/pkg/oidcclient/filesession"
|
||||
@@ -34,13 +40,15 @@ 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)
|
||||
}
|
||||
|
||||
func oidcLoginCommandRealDeps() oidcLoginCommandDeps {
|
||||
return oidcLoginCommandDeps{
|
||||
login: oidcclient.Login,
|
||||
lookupEnv: os.LookupEnv,
|
||||
login: oidcclient.Login,
|
||||
exchangeToken: func(ctx context.Context, client *conciergeclient.Client, token string) (*clientauthv1beta1.ExecCredential, error) {
|
||||
return client.ExchangeToken(ctx, token)
|
||||
},
|
||||
@@ -48,22 +56,27 @@ func oidcLoginCommandRealDeps() oidcLoginCommandDeps {
|
||||
}
|
||||
|
||||
type oidcLoginFlags struct {
|
||||
issuer string
|
||||
clientID string
|
||||
listenPort uint16
|
||||
scopes []string
|
||||
skipBrowser bool
|
||||
sessionCachePath string
|
||||
caBundlePaths []string
|
||||
caBundleData []string
|
||||
debugSessionCache bool
|
||||
requestAudience string
|
||||
conciergeEnabled bool
|
||||
conciergeAuthenticatorType string
|
||||
conciergeAuthenticatorName string
|
||||
conciergeEndpoint string
|
||||
conciergeCABundle string
|
||||
conciergeAPIGroupSuffix string
|
||||
issuer string
|
||||
clientID string
|
||||
listenPort uint16
|
||||
scopes []string
|
||||
skipBrowser bool
|
||||
skipListen bool
|
||||
sessionCachePath string
|
||||
caBundlePaths []string
|
||||
caBundleData []string
|
||||
debugSessionCache bool
|
||||
requestAudience string
|
||||
conciergeEnabled bool
|
||||
conciergeAuthenticatorType string
|
||||
conciergeAuthenticatorName string
|
||||
conciergeEndpoint string
|
||||
conciergeCABundle string
|
||||
conciergeAPIGroupSuffix string
|
||||
credentialCachePath string
|
||||
upstreamIdentityProviderName string
|
||||
upstreamIdentityProviderType string
|
||||
upstreamIdentityProviderFlow string
|
||||
}
|
||||
|
||||
func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
||||
@@ -82,19 +95,26 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
||||
cmd.Flags().Uint16Var(&flags.listenPort, "listen-port", 0, "TCP port for localhost listener (authorization code flow only)")
|
||||
cmd.Flags().StringSliceVar(&flags.scopes, "scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped:request-audience"}, "OIDC scopes to request during login")
|
||||
cmd.Flags().BoolVar(&flags.skipBrowser, "skip-browser", false, "Skip opening the browser (just print the URL)")
|
||||
cmd.Flags().BoolVar(&flags.skipListen, "skip-listen", false, "Skip starting a localhost callback listener (manual copy/paste flow only)")
|
||||
cmd.Flags().StringVar(&flags.sessionCachePath, "session-cache", filepath.Join(mustGetConfigDir(), "sessions.yaml"), "Path to session cache file")
|
||||
cmd.Flags().StringSliceVar(&flags.caBundlePaths, "ca-bundle", nil, "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)")
|
||||
cmd.Flags().StringSliceVar(&flags.caBundleData, "ca-bundle-data", nil, "Base64 endcoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)")
|
||||
cmd.Flags().StringSliceVar(&flags.caBundleData, "ca-bundle-data", nil, "Base64 encoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)")
|
||||
cmd.Flags().BoolVar(&flags.debugSessionCache, "debug-session-cache", false, "Print debug logs related to the session cache")
|
||||
cmd.Flags().StringVar(&flags.requestAudience, "request-audience", "", "Request a token with an alternate audience using RFC8693 token exchange")
|
||||
cmd.Flags().BoolVar(&flags.conciergeEnabled, "enable-concierge", false, "Exchange the OIDC ID token with the Pinniped concierge during login")
|
||||
cmd.Flags().StringVar(&conciergeNamespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the concierge was installed")
|
||||
cmd.Flags().BoolVar(&flags.conciergeEnabled, "enable-concierge", false, "Use the Concierge to login")
|
||||
cmd.Flags().StringVar(&conciergeNamespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the Concierge was installed")
|
||||
cmd.Flags().StringVar(&flags.conciergeAuthenticatorType, "concierge-authenticator-type", "", "Concierge authenticator type (e.g., 'webhook', 'jwt')")
|
||||
cmd.Flags().StringVar(&flags.conciergeAuthenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name")
|
||||
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Pinniped concierge endpoint")
|
||||
cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the concierge")
|
||||
cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", "pinniped.dev", "Concierge API group suffix")
|
||||
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Concierge endpoint")
|
||||
cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the Concierge")
|
||||
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.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) }
|
||||
@@ -105,7 +125,12 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLoginFlags) error {
|
||||
func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLoginFlags) error { //nolint:funlen
|
||||
pLogger, err := SetLogLevel(deps.lookupEnv)
|
||||
if err != nil {
|
||||
plog.WarningErr("Received error while setting log level", err)
|
||||
}
|
||||
|
||||
// Initialize the session cache.
|
||||
var sessionOptions []filesession.Option
|
||||
|
||||
@@ -121,6 +146,7 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
// Initialize the login handler.
|
||||
opts := []oidcclient.Option{
|
||||
oidcclient.WithContext(cmd.Context()),
|
||||
oidcclient.WithLogger(klogr.New()),
|
||||
oidcclient.WithScopes(flags.scopes),
|
||||
oidcclient.WithSessionCache(sessionCache),
|
||||
}
|
||||
@@ -133,6 +159,20 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
opts = append(opts, oidcclient.WithRequestAudience(flags.requestAudience))
|
||||
}
|
||||
|
||||
if flags.upstreamIdentityProviderName != "" {
|
||||
opts = append(opts, oidcclient.WithUpstreamIdentityProvider(
|
||||
flags.upstreamIdentityProviderName, flags.upstreamIdentityProviderType))
|
||||
}
|
||||
|
||||
flowOpts, err := flowOptions(
|
||||
idpdiscoveryv1alpha1.IDPType(flags.upstreamIdentityProviderType),
|
||||
idpdiscoveryv1alpha1.IDPFlow(flags.upstreamIdentityProviderFlow),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, flowOpts...)
|
||||
|
||||
var concierge *conciergeclient.Client
|
||||
if flags.conciergeEnabled {
|
||||
var err error
|
||||
@@ -143,16 +183,18 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid concierge parameters: %w", err)
|
||||
return fmt.Errorf("invalid Concierge parameters: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --skip-browser replaces the default "browser open" function with one that prints to stderr.
|
||||
// --skip-browser skips opening the browser.
|
||||
if flags.skipBrowser {
|
||||
opts = append(opts, oidcclient.WithBrowserOpen(func(url string) error {
|
||||
cmd.PrintErr("Please log in: ", url, "\n")
|
||||
return nil
|
||||
}))
|
||||
opts = append(opts, oidcclient.WithSkipBrowserOpen())
|
||||
}
|
||||
|
||||
// --skip-listen skips starting the localhost callback listener.
|
||||
if flags.skipListen {
|
||||
opts = append(opts, oidcclient.WithSkipListen())
|
||||
}
|
||||
|
||||
if len(flags.caBundlePaths) > 0 || len(flags.caBundleData) > 0 {
|
||||
@@ -162,7 +204,24 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
}
|
||||
opts = append(opts, oidcclient.WithClient(client))
|
||||
}
|
||||
// Look up cached credentials based on a hash of all the CLI arguments and the cluster info.
|
||||
cacheKey := struct {
|
||||
Args []string `json:"args"`
|
||||
ClusterInfo *clientauthv1beta1.Cluster `json:"cluster"`
|
||||
}{
|
||||
Args: os.Args[1:],
|
||||
ClusterInfo: loadClusterInfo(),
|
||||
}
|
||||
var credCache *execcredcache.Cache
|
||||
if flags.credentialCachePath != "" {
|
||||
credCache = execcredcache.New(flags.credentialCachePath)
|
||||
if cred := credCache.Get(cacheKey); cred != nil {
|
||||
pLogger.Debug("using cached cluster credential.")
|
||||
return json.NewEncoder(cmd.OutOrStdout()).Encode(cred)
|
||||
}
|
||||
}
|
||||
|
||||
pLogger.Debug("Performing OIDC login", "issuer", flags.issuer, "client id", flags.clientID)
|
||||
// Do the basic login to get an OIDC token.
|
||||
token, err := deps.login(flags.issuer, flags.clientID, opts...)
|
||||
if err != nil {
|
||||
@@ -171,17 +230,68 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
cred := tokenCredential(token)
|
||||
|
||||
// If the concierge was configured, exchange the credential for a separate short-lived, cluster-specific credential.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if concierge != nil {
|
||||
pLogger.Debug("Exchanging token for cluster credential", "endpoint", flags.conciergeEndpoint, "authenticator type", flags.conciergeAuthenticatorType, "authenticator name", flags.conciergeAuthenticatorName)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cred, err = deps.exchangeToken(ctx, concierge, token.IDToken.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not complete concierge credential exchange: %w", err)
|
||||
return fmt.Errorf("could not complete Concierge credential exchange: %w", err)
|
||||
}
|
||||
pLogger.Debug("Successfully exchanged token for cluster credential.")
|
||||
} else {
|
||||
pLogger.Debug("No concierge configured, skipping token credential exchange")
|
||||
}
|
||||
|
||||
// If there was a credential cache, save the resulting credential for future use.
|
||||
if credCache != nil {
|
||||
pLogger.Debug("caching cluster credential for future use.")
|
||||
credCache.Put(cacheKey, cred)
|
||||
}
|
||||
return json.NewEncoder(cmd.OutOrStdout()).Encode(cred)
|
||||
}
|
||||
|
||||
func flowOptions(requestedIDPType idpdiscoveryv1alpha1.IDPType, requestedFlow idpdiscoveryv1alpha1.IDPFlow) ([]oidcclient.Option, error) {
|
||||
useCLIFlow := []oidcclient.Option{oidcclient.WithCLISendingCredentials()}
|
||||
|
||||
switch requestedIDPType {
|
||||
case idpdiscoveryv1alpha1.IDPTypeOIDC:
|
||||
switch requestedFlow {
|
||||
case idpdiscoveryv1alpha1.IDPFlowCLIPassword:
|
||||
return useCLIFlow, nil
|
||||
case idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode, "":
|
||||
return nil, nil // browser authcode flow is the default Option, so don't need to return an Option here
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"--upstream-identity-provider-flow value not recognized for identity provider type %q: %s (supported values: %s)",
|
||||
requestedIDPType, requestedFlow, strings.Join([]string{idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode.String(), idpdiscoveryv1alpha1.IDPFlowCLIPassword.String()}, ", "))
|
||||
}
|
||||
case idpdiscoveryv1alpha1.IDPTypeLDAP, idpdiscoveryv1alpha1.IDPTypeActiveDirectory:
|
||||
switch requestedFlow {
|
||||
case idpdiscoveryv1alpha1.IDPFlowCLIPassword, "":
|
||||
return useCLIFlow, nil
|
||||
case idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode:
|
||||
fallthrough // not supported for LDAP providers, so fallthrough to error case
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"--upstream-identity-provider-flow value not recognized for identity provider type %q: %s (supported values: %s)",
|
||||
requestedIDPType, requestedFlow, []string{idpdiscoveryv1alpha1.IDPFlowCLIPassword.String()})
|
||||
}
|
||||
default:
|
||||
// Surprisingly cobra does not support this kind of flag validation. See https://github.com/spf13/pflag/issues/236
|
||||
return nil, fmt.Errorf(
|
||||
"--upstream-identity-provider-type value not recognized: %s (supported values: %s)",
|
||||
requestedIDPType,
|
||||
strings.Join([]string{
|
||||
idpdiscoveryv1alpha1.IDPTypeOIDC.String(),
|
||||
idpdiscoveryv1alpha1.IDPTypeLDAP.String(),
|
||||
idpdiscoveryv1alpha1.IDPTypeActiveDirectory.String(),
|
||||
}, ", "),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, error) {
|
||||
pool := x509.NewCertPool()
|
||||
for _, p := range caBundlePaths {
|
||||
@@ -198,7 +308,7 @@ func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, er
|
||||
}
|
||||
pool.AppendCertsFromPEM(pem)
|
||||
}
|
||||
return &http.Client{
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
@@ -206,7 +316,10 @@ func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, er
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
client.Transport = transport.DebugWrappers(client.Transport)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func tokenCredential(token *oidctypes.Token) *clientauthv1beta1.ExecCredential {
|
||||
@@ -225,6 +338,18 @@ func tokenCredential(token *oidctypes.Token) *clientauthv1beta1.ExecCredential {
|
||||
return &cred
|
||||
}
|
||||
|
||||
func SetLogLevel(lookupEnv func(string) (string, bool)) (*plog.PLogger, error) {
|
||||
debug, _ := lookupEnv("PINNIPED_DEBUG")
|
||||
if debug == "true" {
|
||||
err := plog.ValidateAndSetLogLevelGlobally(plog.LevelDebug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
logger := plog.New("Pinniped login: ")
|
||||
return &logger, nil
|
||||
}
|
||||
|
||||
// mustGetConfigDir returns a directory that follows the XDG base directory convention:
|
||||
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should
|
||||
// be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used.
|
||||
|
||||
@@ -6,7 +6,6 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -17,10 +16,12 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"go.pinniped.dev/internal/certauthority"
|
||||
"go.pinniped.dev/internal/here"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/internal/testutil/testlogger"
|
||||
"go.pinniped.dev/pkg/conciergeclient"
|
||||
"go.pinniped.dev/pkg/oidcclient"
|
||||
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
||||
@@ -29,7 +30,7 @@ import (
|
||||
func TestLoginOIDCCommand(t *testing.T) {
|
||||
cfgDir := mustGetConfigDir()
|
||||
|
||||
testCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour)
|
||||
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||
require.NoError(t, err)
|
||||
tmpdir := testutil.TempDir(t)
|
||||
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
||||
@@ -42,10 +43,12 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
args []string
|
||||
loginErr error
|
||||
conciergeErr error
|
||||
env map[string]string
|
||||
wantError bool
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
wantOptionsCount int
|
||||
wantLogs []string
|
||||
}{
|
||||
{
|
||||
name: "help flag passed",
|
||||
@@ -57,22 +60,26 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
oidc --issuer ISSUER [flags]
|
||||
|
||||
Flags:
|
||||
--ca-bundle strings Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
|
||||
--ca-bundle-data strings Base64 endcoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)
|
||||
--client-id string OpenID Connect client ID (default "pinniped-cli")
|
||||
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
||||
--concierge-authenticator-name string Concierge authenticator name
|
||||
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt')
|
||||
--concierge-ca-bundle-data string CA bundle to use when connecting to the concierge
|
||||
--concierge-endpoint string API base for the Pinniped concierge endpoint
|
||||
--enable-concierge Exchange the OIDC ID token with the Pinniped concierge during login
|
||||
-h, --help help for oidc
|
||||
--issuer string OpenID Connect issuer URL
|
||||
--listen-port uint16 TCP port for localhost listener (authorization code flow only)
|
||||
--request-audience string Request a token with an alternate audience using RFC8693 token exchange
|
||||
--scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped:request-audience])
|
||||
--session-cache string Path to session cache file (default "` + cfgDir + `/sessions.yaml")
|
||||
--skip-browser Skip opening the browser (just print the URL)
|
||||
--ca-bundle strings Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
|
||||
--ca-bundle-data strings Base64 encoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)
|
||||
--client-id string OpenID Connect client ID (default "pinniped-cli")
|
||||
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
||||
--concierge-authenticator-name string Concierge authenticator name
|
||||
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt')
|
||||
--concierge-ca-bundle-data string CA bundle to use when connecting to the Concierge
|
||||
--concierge-endpoint string API base for the Concierge endpoint
|
||||
--credential-cache string Path to cluster-specific credentials cache ("" disables the cache) (default "` + cfgDir + `/credentials.yaml")
|
||||
--enable-concierge Use the Concierge to login
|
||||
-h, --help help for oidc
|
||||
--issuer string OpenID Connect issuer URL
|
||||
--listen-port uint16 TCP port for localhost listener (authorization code flow only)
|
||||
--request-audience string Request a token with an alternate audience using RFC8693 token exchange
|
||||
--scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped:request-audience])
|
||||
--session-cache string Path to session cache file (default "` + cfgDir + `/sessions.yaml")
|
||||
--skip-browser Skip opening the browser (just print the URL)
|
||||
--upstream-identity-provider-flow string The type of client flow to use with the upstream identity provider during login with a Supervisor (e.g. 'browser_authcode', 'cli_password')
|
||||
--upstream-identity-provider-name string The name of the upstream identity provider used during login with a Supervisor
|
||||
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap', 'activedirectory') (default "oidc")
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -92,7 +99,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: invalid concierge parameters: endpoint must not be empty
|
||||
Error: invalid Concierge parameters: endpoint must not be empty
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -120,7 +127,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "invalid api group suffix",
|
||||
name: "invalid API group suffix",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--enable-concierge",
|
||||
@@ -131,7 +138,141 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
},
|
||||
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])?)*')
|
||||
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",
|
||||
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: "oidc upstream type with default flow is allowed",
|
||||
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
|
||||
},
|
||||
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",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "oidc",
|
||||
"--upstream-identity-provider-flow", "cli_password",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 5,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "oidc upstream type with browser flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "oidc",
|
||||
"--upstream-identity-provider-flow", "browser_authcode",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 4,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "oidc upstream type with unsupported flow is an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "oidc",
|
||||
"--upstream-identity-provider-flow", "foobar",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: --upstream-identity-provider-flow value not recognized for identity provider type "oidc": foobar (supported values: browser_authcode, cli_password)
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "ldap upstream type with default flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--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: "activedirectory upstream type with default flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 5,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "ldap upstream type with CLI flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--upstream-identity-provider-flow", "cli_password",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 5,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "ldap upstream type with unsupported flow is an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
"--upstream-identity-provider-flow", "browser_authcode", // "browser_authcode" is only supported for OIDC upstreams
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: --upstream-identity-provider-flow value not recognized for identity provider type "ldap": browser_authcode (supported values: [cli_password])
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "active directory upstream type with CLI flow is allowed",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--upstream-identity-provider-flow", "cli_password",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantOptionsCount: 5,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "active directory upstream type with unsupported flow is an error",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--client-id", "test-client-id",
|
||||
"--upstream-identity-provider-type", "activedirectory",
|
||||
"--upstream-identity-provider-flow", "browser_authcode", // "browser_authcode" is only supported for OIDC upstreams
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: --upstream-identity-provider-flow value not recognized for identity provider type "activedirectory": browser_authcode (supported values: [cli_password])
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -139,9 +280,10 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
args: []string{
|
||||
"--client-id", "test-client-id",
|
||||
"--issuer", "test-issuer",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
loginErr: fmt.Errorf("some login error"),
|
||||
wantOptionsCount: 3,
|
||||
wantOptionsCount: 4,
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not complete Pinniped login: some login error
|
||||
@@ -156,12 +298,13 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--concierge-authenticator-type", "jwt",
|
||||
"--concierge-authenticator-name", "test-authenticator",
|
||||
"--concierge-endpoint", "https://127.0.0.1:1234/",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
conciergeErr: fmt.Errorf("some concierge error"),
|
||||
wantOptionsCount: 3,
|
||||
wantOptionsCount: 4,
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not complete concierge credential exchange: some concierge error
|
||||
Error: could not complete Concierge credential exchange: some concierge error
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -169,9 +312,15 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
args: []string{
|
||||
"--client-id", "test-client-id",
|
||||
"--issuer", "test-issuer",
|
||||
"--credential-cache", "", // must specify --credential-cache or else the cache file on disk causes test pollution
|
||||
},
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
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{
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: Performing OIDC login\" \"client id\"=\"test-client-id\" \"issuer\"=\"test-issuer\"",
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: No concierge configured, skipping token credential exchange\"",
|
||||
},
|
||||
wantOptionsCount: 3,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "success with all options",
|
||||
@@ -179,6 +328,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--client-id", "test-client-id",
|
||||
"--issuer", "test-issuer",
|
||||
"--skip-browser",
|
||||
"--skip-listen",
|
||||
"--listen-port", "1234",
|
||||
"--debug-session-cache",
|
||||
"--request-audience", "cluster-1234",
|
||||
@@ -190,18 +340,34 @@ 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
|
||||
"--upstream-identity-provider-name", "some-upstream-name",
|
||||
"--upstream-identity-provider-type", "ldap",
|
||||
},
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantOptionsCount: 11,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"exchanged-token"}}` + "\n",
|
||||
wantLogs: []string{
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: Performing OIDC login\" \"client id\"=\"test-client-id\" \"issuer\"=\"test-issuer\"",
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: Exchanging token for cluster credential\" \"authenticator name\"=\"test-authenticator\" \"authenticator type\"=\"webhook\" \"endpoint\"=\"https://127.0.0.1:1234/\"",
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: Successfully exchanged token for cluster credential.\"",
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: caching cluster credential for future use.\"",
|
||||
},
|
||||
wantOptionsCount: 7,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"exchanged-token"}}` + "\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testLogger := testlogger.New(t)
|
||||
klog.SetLogger(testLogger)
|
||||
var (
|
||||
gotOptions []oidcclient.Option
|
||||
)
|
||||
cmd := oidcLoginCommand(oidcLoginCommandDeps{
|
||||
lookupEnv: func(s string) (string, bool) {
|
||||
v, ok := tt.env[s]
|
||||
return v, ok
|
||||
},
|
||||
login: func(issuer string, clientID string, opts ...oidcclient.Option) (*oidctypes.Token, error) {
|
||||
require.Equal(t, "test-issuer", issuer)
|
||||
require.Equal(t, "test-client-id", clientID)
|
||||
@@ -247,6 +413,8 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
require.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
|
||||
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
|
||||
require.Len(t, gotOptions, tt.wantOptionsCount)
|
||||
|
||||
require.Equal(t, tt.wantLogs, testLogger.Lines())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,15 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
|
||||
"go.pinniped.dev/internal/execcredcache"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/pkg/conciergeclient"
|
||||
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
||||
)
|
||||
@@ -46,6 +50,7 @@ type staticLoginParams struct {
|
||||
conciergeEndpoint string
|
||||
conciergeCABundle string
|
||||
conciergeAPIGroupSuffix string
|
||||
credentialCachePath string
|
||||
}
|
||||
|
||||
func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
|
||||
@@ -61,13 +66,15 @@ func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
|
||||
)
|
||||
cmd.Flags().StringVar(&flags.staticToken, "token", "", "Static token to present during login")
|
||||
cmd.Flags().StringVar(&flags.staticTokenEnvName, "token-env", "", "Environment variable containing a static token")
|
||||
cmd.Flags().BoolVar(&flags.conciergeEnabled, "enable-concierge", false, "Exchange the token with the Pinniped concierge during login")
|
||||
cmd.Flags().StringVar(&conciergeNamespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the concierge was installed")
|
||||
cmd.Flags().BoolVar(&flags.conciergeEnabled, "enable-concierge", false, "Use the Concierge to login")
|
||||
cmd.Flags().StringVar(&conciergeNamespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the Concierge was installed")
|
||||
cmd.Flags().StringVar(&flags.conciergeAuthenticatorType, "concierge-authenticator-type", "", "Concierge authenticator type (e.g., 'webhook', 'jwt')")
|
||||
cmd.Flags().StringVar(&flags.conciergeAuthenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name")
|
||||
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Pinniped concierge endpoint")
|
||||
cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the concierge")
|
||||
cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", "pinniped.dev", "Concierge API group suffix")
|
||||
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Concierge endpoint")
|
||||
cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the Concierge")
|
||||
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.OutOrStdout(), deps, flags) }
|
||||
|
||||
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
|
||||
@@ -77,6 +84,11 @@ func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
|
||||
}
|
||||
|
||||
func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams) error {
|
||||
pLogger, err := SetLogLevel(deps.lookupEnv)
|
||||
if err != nil {
|
||||
plog.WarningErr("Received error while setting log level", err)
|
||||
}
|
||||
|
||||
if flags.staticToken == "" && flags.staticTokenEnvName == "" {
|
||||
return fmt.Errorf("one of --token or --token-env must be set")
|
||||
}
|
||||
@@ -91,7 +103,7 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams
|
||||
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid concierge parameters: %w", err)
|
||||
return fmt.Errorf("invalid Concierge parameters: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,16 +123,44 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams
|
||||
}
|
||||
cred := tokenCredential(&oidctypes.Token{IDToken: &oidctypes.IDToken{Token: token}})
|
||||
|
||||
// Exchange that token with the concierge, if configured.
|
||||
// Look up cached credentials based on a hash of all the CLI arguments, the current token value, and the cluster info.
|
||||
cacheKey := struct {
|
||||
Args []string `json:"args"`
|
||||
Token string `json:"token"`
|
||||
ClusterInfo *clientauthv1beta1.Cluster `json:"cluster"`
|
||||
}{
|
||||
Args: os.Args[1:],
|
||||
Token: token,
|
||||
ClusterInfo: loadClusterInfo(),
|
||||
}
|
||||
var credCache *execcredcache.Cache
|
||||
if flags.credentialCachePath != "" {
|
||||
credCache = execcredcache.New(flags.credentialCachePath)
|
||||
if cred := credCache.Get(cacheKey); cred != nil {
|
||||
pLogger.Debug("using cached cluster credential.")
|
||||
return json.NewEncoder(out).Encode(cred)
|
||||
}
|
||||
}
|
||||
|
||||
// If the concierge was configured, exchange the credential for a separate short-lived, cluster-specific credential.
|
||||
if concierge != nil {
|
||||
pLogger.Debug("exchanging static token for cluster credential", "endpoint", flags.conciergeEndpoint, "authenticator type", flags.conciergeAuthenticatorType, "authenticator name", flags.conciergeAuthenticatorName)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var err error
|
||||
cred, err = deps.exchangeToken(ctx, concierge, token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not complete concierge credential exchange: %w", err)
|
||||
return fmt.Errorf("could not complete Concierge credential exchange: %w", err)
|
||||
}
|
||||
pLogger.Debug("exchanged static token for cluster credential")
|
||||
}
|
||||
|
||||
// If there was a credential cache, save the resulting credential for future use. We only save to the cache if
|
||||
// the credential came from the concierge, since that's the only static token case where the cache is useful.
|
||||
if credCache != nil && concierge != nil {
|
||||
credCache.Put(cacheKey, cred)
|
||||
}
|
||||
|
||||
return json.NewEncoder(out).Encode(cred)
|
||||
}
|
||||
|
||||
@@ -6,13 +6,16 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"go.pinniped.dev/internal/testutil/testlogger"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
@@ -24,7 +27,9 @@ import (
|
||||
)
|
||||
|
||||
func TestLoginStaticCommand(t *testing.T) {
|
||||
testCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour)
|
||||
cfgDir := mustGetConfigDir()
|
||||
|
||||
testCA, err := certauthority.New("Test CA", 1*time.Hour)
|
||||
require.NoError(t, err)
|
||||
tmpdir := testutil.TempDir(t)
|
||||
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
|
||||
@@ -40,6 +45,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
wantOptionsCount int
|
||||
wantLogs []string
|
||||
}{
|
||||
{
|
||||
name: "help flag passed",
|
||||
@@ -54,9 +60,10 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
|
||||
--concierge-authenticator-name string Concierge authenticator name
|
||||
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt')
|
||||
--concierge-ca-bundle-data string CA bundle to use when connecting to the concierge
|
||||
--concierge-endpoint string API base for the Pinniped concierge endpoint
|
||||
--enable-concierge Exchange the token with the Pinniped concierge during login
|
||||
--concierge-ca-bundle-data string CA bundle to use when connecting to the Concierge
|
||||
--concierge-endpoint string API base for the Concierge endpoint
|
||||
--credential-cache string Path to cluster-specific credentials cache ("" disables the cache) (default "` + cfgDir + `/credentials.yaml")
|
||||
--enable-concierge Use the Concierge to login
|
||||
-h, --help help for static
|
||||
--token string Static token to present during login
|
||||
--token-env string Environment variable containing a static token
|
||||
@@ -78,7 +85,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: invalid concierge parameters: endpoint must not be empty
|
||||
Error: invalid Concierge parameters: endpoint must not be empty
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -112,7 +119,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
env: map[string]string{
|
||||
"TEST_TOKEN_ENV": "test-token",
|
||||
},
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"test-token"}}` + "\n",
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"test-token"}}` + "\n",
|
||||
},
|
||||
{
|
||||
name: "concierge failure",
|
||||
@@ -124,13 +131,15 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
"--concierge-authenticator-name", "test-authenticator",
|
||||
},
|
||||
conciergeErr: fmt.Errorf("some concierge error"),
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not complete concierge credential exchange: some concierge error
|
||||
Error: could not complete Concierge credential exchange: some concierge error
|
||||
`),
|
||||
wantLogs: []string{"\"level\"=0 \"msg\"=\"Pinniped login: exchanging static token for cluster credential\" \"authenticator name\"=\"test-authenticator\" \"authenticator type\"=\"webhook\" \"endpoint\"=\"https://127.0.0.1/\""},
|
||||
},
|
||||
{
|
||||
name: "invalid api group suffix",
|
||||
name: "invalid API group suffix",
|
||||
args: []string{
|
||||
"--token", "test-token",
|
||||
"--enable-concierge",
|
||||
@@ -141,7 +150,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
},
|
||||
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])?)*')
|
||||
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])?)*')
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -149,12 +158,15 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
args: []string{
|
||||
"--token", "test-token",
|
||||
},
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"test-token"}}` + "\n",
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"test-token"}}` + "\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testLogger := testlogger.New(t)
|
||||
klog.SetLogger(testLogger)
|
||||
cmd := staticLoginCommand(staticLoginDeps{
|
||||
lookupEnv: func(s string) (string, bool) {
|
||||
v, ok := tt.env[s]
|
||||
@@ -190,6 +202,8 @@ 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, testLogger.Lines())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
20
cmd/pinniped/cmd/testdata/kubeconfig.yaml
vendored
20
cmd/pinniped/cmd/testdata/kubeconfig.yaml
vendored
@@ -3,25 +3,33 @@ clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ== # fake-certificate-authority-data-value
|
||||
server: https://fake-server-url-value
|
||||
name: kind-kind
|
||||
name: kind-cluster
|
||||
- cluster:
|
||||
certificate-authority-data: c29tZS1vdGhlci1mYWtlLWNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhLXZhbHVl # some-other-fake-certificate-authority-data-value
|
||||
server: https://some-other-fake-server-url-value
|
||||
name: some-other-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kind-kind
|
||||
user: kind-kind
|
||||
name: kind-kind
|
||||
cluster: kind-cluster
|
||||
user: kind-user
|
||||
name: kind-context
|
||||
- context:
|
||||
cluster: some-other-cluster
|
||||
user: some-other-user
|
||||
name: some-other-context
|
||||
current-context: kind-kind
|
||||
- context:
|
||||
cluster: invalid-cluster
|
||||
user: some-other-user
|
||||
name: invalid-context-no-such-cluster
|
||||
- context:
|
||||
cluster: some-other-cluster
|
||||
user: invalid-user
|
||||
name: invalid-context-no-such-user
|
||||
current-context: kind-context
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: kind-kind
|
||||
- name: kind-user
|
||||
user:
|
||||
client-certificate-data: ZmFrZS1jbGllbnQtY2VydGlmaWNhdGUtZGF0YS12YWx1ZQ== # fake-client-certificate-data-value
|
||||
client-key-data: ZmFrZS1jbGllbnQta2V5LWRhdGEtdmFsdWU= # fake-client-key-data-value
|
||||
|
||||
191
cmd/pinniped/cmd/whoami.go
Normal file
191
cmd/pinniped/cmd/whoami.go
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"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"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
|
||||
conciergescheme "go.pinniped.dev/internal/concierge/scheme"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
"go.pinniped.dev/internal/here"
|
||||
)
|
||||
|
||||
//nolint: gochecknoinits
|
||||
func init() {
|
||||
rootCmd.AddCommand(newWhoamiCommand(getRealConciergeClientset))
|
||||
}
|
||||
|
||||
type whoamiFlags struct {
|
||||
outputFormat string // e.g., yaml, json, text
|
||||
|
||||
kubeconfigPath string
|
||||
kubeconfigContextOverride string
|
||||
|
||||
apiGroupSuffix string
|
||||
}
|
||||
|
||||
type clusterInfo struct {
|
||||
name string
|
||||
url string
|
||||
}
|
||||
|
||||
func newWhoamiCommand(getClientset getConciergeClientsetFunc) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Args: cobra.NoArgs, // do not accept positional arguments for this command
|
||||
Use: "whoami",
|
||||
Short: "Print information about the current user",
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags := &whoamiFlags{}
|
||||
|
||||
// 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.kubeconfigContextOverride, "kubeconfig-context", "", "Kubeconfig context name (default: current active context)")
|
||||
f.StringVar(&flags.apiGroupSuffix, "api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix")
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, _ []string) error {
|
||||
return runWhoami(cmd.OutOrStdout(), getClientset, flags)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runWhoami(output io.Writer, getClientset getConciergeClientsetFunc, flags *whoamiFlags) error {
|
||||
clientConfig := newClientConfig(flags.kubeconfigPath, flags.kubeconfigContextOverride)
|
||||
clientset, err := getClientset(clientConfig, flags.apiGroupSuffix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not configure Kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
clusterInfo, err := getCurrentCluster(clientConfig, flags.kubeconfigContextOverride)
|
||||
if err != nil {
|
||||
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{})
|
||||
if err != nil {
|
||||
hint := ""
|
||||
if errors.IsNotFound(err) {
|
||||
hint = " (is the Pinniped WhoAmI API running and healthy?)"
|
||||
}
|
||||
return fmt.Errorf("could not complete WhoAmIRequest%s: %w", hint, err)
|
||||
}
|
||||
|
||||
if err := writeWhoamiOutput(output, flags, clusterInfo, whoAmI); err != nil {
|
||||
return fmt.Errorf("could not write output: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCurrentCluster(clientConfig clientcmd.ClientConfig, currentContextNameOverride string) (*clusterInfo, error) {
|
||||
currentKubeConfig, err := clientConfig.RawConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contextName := currentKubeConfig.CurrentContext
|
||||
if len(currentContextNameOverride) > 0 {
|
||||
contextName = currentContextNameOverride
|
||||
}
|
||||
|
||||
unknownClusterInfo := &clusterInfo{name: "???", url: "???"}
|
||||
ctx, ok := currentKubeConfig.Contexts[contextName]
|
||||
if !ok {
|
||||
return unknownClusterInfo, nil
|
||||
}
|
||||
|
||||
cluster, ok := currentKubeConfig.Clusters[ctx.Cluster]
|
||||
if !ok {
|
||||
return unknownClusterInfo, nil
|
||||
}
|
||||
|
||||
return &clusterInfo{name: ctx.Cluster, url: cluster.Server}, nil
|
||||
}
|
||||
|
||||
func writeWhoamiOutput(output io.Writer, flags *whoamiFlags, cInfo *clusterInfo, whoAmI *identityv1alpha1.WhoAmIRequest) error {
|
||||
switch flags.outputFormat {
|
||||
case "text":
|
||||
return writeWhoamiOutputText(output, cInfo, whoAmI)
|
||||
case "json":
|
||||
return writeWhoamiOutputJSON(output, flags.apiGroupSuffix, whoAmI)
|
||||
case "yaml":
|
||||
return writeWhoamiOutputYAML(output, flags.apiGroupSuffix, whoAmI)
|
||||
default:
|
||||
return fmt.Errorf("unknown output format: %q", flags.outputFormat)
|
||||
}
|
||||
}
|
||||
|
||||
func writeWhoamiOutputText(output io.Writer, clusterInfo *clusterInfo, whoAmI *identityv1alpha1.WhoAmIRequest) error {
|
||||
fmt.Fprint(output, here.Docf(`
|
||||
Current cluster info:
|
||||
|
||||
Name: %s
|
||||
URL: %s
|
||||
|
||||
Current user info:
|
||||
|
||||
Username: %s
|
||||
Groups: %s
|
||||
`, clusterInfo.name, clusterInfo.url, whoAmI.Status.KubernetesUserInfo.User.Username, prettyStrings(whoAmI.Status.KubernetesUserInfo.User.Groups)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeWhoamiOutputJSON(output io.Writer, apiGroupSuffix string, whoAmI *identityv1alpha1.WhoAmIRequest) error {
|
||||
return serialize(output, apiGroupSuffix, whoAmI, runtime.ContentTypeJSON)
|
||||
}
|
||||
|
||||
func writeWhoamiOutputYAML(output io.Writer, apiGroupSuffix string, whoAmI *identityv1alpha1.WhoAmIRequest) error {
|
||||
return serialize(output, apiGroupSuffix, whoAmI, runtime.ContentTypeYAML)
|
||||
}
|
||||
|
||||
func serialize(output io.Writer, apiGroupSuffix string, whoAmI *identityv1alpha1.WhoAmIRequest, contentType string) error {
|
||||
scheme, _, identityGV := conciergescheme.New(apiGroupSuffix)
|
||||
codecs := serializer.NewCodecFactory(scheme)
|
||||
respInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), contentType)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown content type: %q", contentType)
|
||||
}
|
||||
|
||||
// I have seen the pretty serializer be nil before, so this will hopefully protect against that
|
||||
// corner.
|
||||
serializer := respInfo.PrettySerializer
|
||||
if serializer == nil {
|
||||
serializer = respInfo.Serializer
|
||||
}
|
||||
|
||||
// Ensure that these fields are set so that the JSON/YAML output tells the full story.
|
||||
whoAmI.APIVersion = identityGV.String()
|
||||
whoAmI.Kind = "WhoAmIRequest"
|
||||
|
||||
return serializer.Encode(whoAmI, output)
|
||||
}
|
||||
|
||||
func prettyStrings(ss []string) string {
|
||||
b := &strings.Builder{}
|
||||
for i, s := range ss {
|
||||
if i != 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(s)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
327
cmd/pinniped/cmd/whoami_test.go
Normal file
327
cmd/pinniped/cmd/whoami_test.go
Normal file
@@ -0,0 +1,327 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kubetesting "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
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"
|
||||
"go.pinniped.dev/internal/constable"
|
||||
"go.pinniped.dev/internal/here"
|
||||
)
|
||||
|
||||
func TestWhoami(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
groupsOverride []string
|
||||
gettingClientsetErr error
|
||||
callingAPIErr error
|
||||
wantError bool
|
||||
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: "text output",
|
||||
args: []string{"--kubeconfig", "testdata/kubeconfig.yaml"},
|
||||
wantStdout: here.Doc(`
|
||||
Current cluster info:
|
||||
|
||||
Name: kind-cluster
|
||||
URL: https://fake-server-url-value
|
||||
|
||||
Current user info:
|
||||
|
||||
Username: some-username
|
||||
Groups: some-group-0, some-group-1
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "text output with long output flag",
|
||||
args: []string{"--kubeconfig", "testdata/kubeconfig.yaml", "--output", "text"},
|
||||
wantStdout: here.Doc(`
|
||||
Current cluster info:
|
||||
|
||||
Name: kind-cluster
|
||||
URL: https://fake-server-url-value
|
||||
|
||||
Current user info:
|
||||
|
||||
Username: some-username
|
||||
Groups: some-group-0, some-group-1
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "text output with 1 group",
|
||||
args: []string{"--kubeconfig", "testdata/kubeconfig.yaml", "--output", "text"},
|
||||
groupsOverride: []string{"some-group-0"},
|
||||
wantStdout: here.Doc(`
|
||||
Current cluster info:
|
||||
|
||||
Name: kind-cluster
|
||||
URL: https://fake-server-url-value
|
||||
|
||||
Current user info:
|
||||
|
||||
Username: some-username
|
||||
Groups: some-group-0
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "text output with no groups",
|
||||
args: []string{"--kubeconfig", "testdata/kubeconfig.yaml", "--output", "text"},
|
||||
groupsOverride: []string{},
|
||||
wantStdout: here.Doc(`
|
||||
Current cluster info:
|
||||
|
||||
Name: kind-cluster
|
||||
URL: https://fake-server-url-value
|
||||
|
||||
Current user info:
|
||||
|
||||
Username: some-username
|
||||
Groups:
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "json output",
|
||||
args: []string{"--kubeconfig", "testdata/kubeconfig.yaml", "-o", "json"},
|
||||
wantStdout: here.Doc(`
|
||||
{
|
||||
"kind": "WhoAmIRequest",
|
||||
"apiVersion": "identity.concierge.pinniped.dev/v1alpha1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"spec": {},
|
||||
"status": {
|
||||
"kubernetesUserInfo": {
|
||||
"user": {
|
||||
"username": "some-username",
|
||||
"groups": [
|
||||
"some-group-0",
|
||||
"some-group-1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
},
|
||||
{
|
||||
name: "json output with api group suffix flag",
|
||||
args: []string{"--kubeconfig", "testdata/kubeconfig.yaml", "-o", "json", "--api-group-suffix", "tuna.io"},
|
||||
wantStdout: here.Doc(`
|
||||
{
|
||||
"kind": "WhoAmIRequest",
|
||||
"apiVersion": "identity.concierge.tuna.io/v1alpha1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"spec": {},
|
||||
"status": {
|
||||
"kubernetesUserInfo": {
|
||||
"user": {
|
||||
"username": "some-username",
|
||||
"groups": [
|
||||
"some-group-0",
|
||||
"some-group-1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
},
|
||||
{
|
||||
name: "yaml output",
|
||||
args: []string{"--kubeconfig", "testdata/kubeconfig.yaml", "-o", "yaml"},
|
||||
wantStdout: here.Doc(`
|
||||
apiVersion: identity.concierge.pinniped.dev/v1alpha1
|
||||
kind: WhoAmIRequest
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
spec: {}
|
||||
status:
|
||||
kubernetesUserInfo:
|
||||
user:
|
||||
groups:
|
||||
- some-group-0
|
||||
- some-group-1
|
||||
username: some-username
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "yaml output with api group suffix",
|
||||
args: []string{"--kubeconfig", "testdata/kubeconfig.yaml", "-o", "yaml", "--api-group-suffix", "tuna.io"},
|
||||
wantStdout: here.Doc(`
|
||||
apiVersion: identity.concierge.tuna.io/v1alpha1
|
||||
kind: WhoAmIRequest
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
spec: {}
|
||||
status:
|
||||
kubernetesUserInfo:
|
||||
user:
|
||||
groups:
|
||||
- some-group-0
|
||||
- some-group-1
|
||||
username: some-username
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "extra args",
|
||||
args: []string{"extra-arg"},
|
||||
wantError: true,
|
||||
wantStderr: "Error: unknown command \"extra-arg\" for \"whoami\"\n",
|
||||
},
|
||||
{
|
||||
name: "cannot get cluster info",
|
||||
args: []string{"--kubeconfig", "this-file-does-not-exist"},
|
||||
wantError: true,
|
||||
wantStderr: "Error: could not get current cluster info: stat this-file-does-not-exist: no such file or directory\n",
|
||||
},
|
||||
{
|
||||
name: "different kubeconfig context, but same as current",
|
||||
args: []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--kubeconfig-context", "kind-context",
|
||||
},
|
||||
wantStdout: here.Doc(`
|
||||
Current cluster info:
|
||||
|
||||
Name: kind-cluster
|
||||
URL: https://fake-server-url-value
|
||||
|
||||
Current user info:
|
||||
|
||||
Username: some-username
|
||||
Groups: some-group-0, some-group-1
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "different kubeconfig context, not current",
|
||||
args: []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--kubeconfig-context", "some-other-context",
|
||||
},
|
||||
wantStdout: here.Doc(`
|
||||
Current cluster info:
|
||||
|
||||
Name: some-other-cluster
|
||||
URL: https://some-other-fake-server-url-value
|
||||
|
||||
Current user info:
|
||||
|
||||
Username: some-username
|
||||
Groups: some-group-0, some-group-1
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "invalid kubeconfig context prints ???",
|
||||
args: []string{
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
"--kubeconfig-context", "invalid",
|
||||
},
|
||||
wantStdout: here.Doc(`
|
||||
Current cluster info:
|
||||
|
||||
Name: ???
|
||||
URL: ???
|
||||
|
||||
Current user info:
|
||||
|
||||
Username: some-username
|
||||
Groups: some-group-0, some-group-1
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "getting clientset fails",
|
||||
gettingClientsetErr: constable.Error("some get clientset error"),
|
||||
wantError: true,
|
||||
wantStderr: "Error: could not configure Kubernetes client: some get clientset error\n",
|
||||
},
|
||||
{
|
||||
name: "calling API fails",
|
||||
callingAPIErr: constable.Error("some API error"),
|
||||
wantError: true,
|
||||
wantStderr: "Error: could not complete WhoAmIRequest: some API error\n",
|
||||
},
|
||||
{
|
||||
name: "calling API fails because WhoAmI API is not installed",
|
||||
callingAPIErr: errors.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) {
|
||||
if test.gettingClientsetErr != nil {
|
||||
return nil, test.gettingClientsetErr
|
||||
}
|
||||
clientset := fakeconciergeclientset.NewSimpleClientset()
|
||||
clientset.PrependReactor("create", "whoamirequests", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
if test.callingAPIErr != nil {
|
||||
return true, nil, test.callingAPIErr
|
||||
}
|
||||
groups := []string{"some-group-0", "some-group-1"}
|
||||
if test.groupsOverride != nil {
|
||||
groups = test.groupsOverride
|
||||
}
|
||||
return true, &identityv1alpha1.WhoAmIRequest{
|
||||
Status: identityv1alpha1.WhoAmIRequestStatus{
|
||||
KubernetesUserInfo: identityv1alpha1.KubernetesUserInfo{
|
||||
User: identityv1alpha1.UserInfo{
|
||||
Username: "some-username",
|
||||
Groups: groups,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return clientset, nil
|
||||
}
|
||||
cmd := newWhoamiCommand(getClientset)
|
||||
|
||||
stdout, stderr := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
|
||||
cmd.SetOut(stdout)
|
||||
cmd.SetErr(stderr)
|
||||
cmd.SetArgs(test.args)
|
||||
|
||||
err := cmd.Execute()
|
||||
if test.wantError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, test.wantStdout, stdout.String())
|
||||
require.Equal(t, test.wantStderr, stderr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,20 @@
|
||||
|
||||
package main
|
||||
|
||||
import "go.pinniped.dev/cmd/pinniped/cmd"
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/browser"
|
||||
|
||||
"go.pinniped.dev/cmd/pinniped/cmd"
|
||||
)
|
||||
|
||||
//nolint: gochecknoinits
|
||||
func init() {
|
||||
// browsers like chrome like to write to our std out which breaks our JSON ExecCredential output
|
||||
// thus we redirect the browser's std out to our std err
|
||||
browser.Stdout = os.Stderr
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
|
||||
@@ -1,39 +1,3 @@
|
||||
# Deploying
|
||||
# Pinniped Concierge Deployment
|
||||
|
||||
## Connecting Pinniped to an Identity Provider
|
||||
|
||||
If you would like to try Pinniped, but you don't have a compatible identity provider,
|
||||
you can use Pinniped's test identity provider.
|
||||
See [deploy/local-user-authenticator/README.md](../../deploy/local-user-authenticator/README.md)
|
||||
for details.
|
||||
|
||||
## Installing the Latest Version with Default Options
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-concierge.yaml
|
||||
```
|
||||
|
||||
## Installing a Specific Version with Default Options
|
||||
|
||||
Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number
|
||||
and use it to replace the version number in the URL below.
|
||||
|
||||
```bash
|
||||
# Replace v0.4.1 with your preferred version in the URL below
|
||||
kubectl apply -f https://get.pinniped.dev/v0.4.1/install-pinniped-concierge.yaml
|
||||
```
|
||||
|
||||
## Installing with Custom Options
|
||||
|
||||
Creating your own deployment YAML file requires `ytt` from [Carvel](https://carvel.dev/) to template the YAML files
|
||||
in the `deploy/concierge` directory.
|
||||
Either [install `ytt`](https://get-ytt.io/) or use the [container image from Dockerhub](https://hub.docker.com/r/k14s/image/tags).
|
||||
|
||||
1. `git clone` this repo and `git checkout` the release version tag of the release that you would like to deploy.
|
||||
1. The configuration options are in [deploy/concierge/values.yml](values.yaml).
|
||||
Fill in the values in that file, or override those values using additional `ytt` command-line options in
|
||||
the command below. Use the release version tag as the `image_tag` value.
|
||||
2. In a terminal, cd to this `deploy/concierge` directory
|
||||
3. To generate the final YAML files, run `ytt --file .`
|
||||
4. Deploy the generated YAML using your preferred deployment tool, such as `kubectl` or [`kapp`](https://get-kapp.io/).
|
||||
For example: `ytt --file . | kapp deploy --yes --app pinniped --diff-changes --file -`
|
||||
See [the how-to guide for details](https://pinniped.dev/docs/howto/install-concierge/).
|
||||
|
||||
@@ -21,7 +21,8 @@ spec:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Describes the configuration status of a Pinniped credential issuer.
|
||||
description: CredentialIssuer describes the configuration and status of the
|
||||
Pinniped Concierge credential issuer.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
@@ -35,12 +36,78 @@ spec:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: Spec describes the intended configuration of the Concierge.
|
||||
properties:
|
||||
impersonationProxy:
|
||||
description: ImpersonationProxy describes the intended configuration
|
||||
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\"."
|
||||
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.'
|
||||
enum:
|
||||
- auto
|
||||
- enabled
|
||||
- disabled
|
||||
type: string
|
||||
service:
|
||||
default:
|
||||
type: LoadBalancer
|
||||
description: Service describes the configuration of the Service
|
||||
provisioned to expose the impersonation proxy to clients.
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Annotations specifies zero or more key/value
|
||||
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.
|
||||
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."
|
||||
enum:
|
||||
- LoadBalancer
|
||||
- ClusterIP
|
||||
- None
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- mode
|
||||
- service
|
||||
type: object
|
||||
required:
|
||||
- impersonationProxy
|
||||
type: object
|
||||
status:
|
||||
description: Status of the credential issuer.
|
||||
description: CredentialIssuerStatus describes the status of the Concierge.
|
||||
properties:
|
||||
kubeConfigInfo:
|
||||
description: Information needed to form a valid Pinniped-based kubeconfig
|
||||
using this credential issuer.
|
||||
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.
|
||||
@@ -59,9 +126,62 @@ spec:
|
||||
description: List of integration strategies that were attempted by
|
||||
Pinniped.
|
||||
items:
|
||||
description: Status of an integration strategy that was attempted
|
||||
by Pinniped.
|
||||
description: CredentialIssuerStrategy describes the status of an
|
||||
integration strategy that was attempted by Pinniped.
|
||||
properties:
|
||||
frontend:
|
||||
description: Frontend describes how clients can connect using
|
||||
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".
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: CertificateAuthorityData is the base64-encoded
|
||||
PEM CA bundle of the impersonation proxy.
|
||||
minLength: 1
|
||||
type: string
|
||||
endpoint:
|
||||
description: Endpoint is the HTTPS endpoint of the impersonation
|
||||
proxy.
|
||||
minLength: 1
|
||||
pattern: ^https://
|
||||
type: string
|
||||
required:
|
||||
- certificateAuthorityData
|
||||
- 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".
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: CertificateAuthorityData is the base64-encoded
|
||||
Kubernetes API server CA bundle.
|
||||
minLength: 1
|
||||
type: string
|
||||
server:
|
||||
description: Server is the Kubernetes API server URL.
|
||||
minLength: 1
|
||||
pattern: ^https://|^http://
|
||||
type: string
|
||||
required:
|
||||
- certificateAuthorityData
|
||||
- server
|
||||
type: object
|
||||
type:
|
||||
description: Type describes which frontend mechanism clients
|
||||
can use with a strategy.
|
||||
enum:
|
||||
- TokenCredentialRequestAPI
|
||||
- ImpersonationProxy
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
lastUpdateTime:
|
||||
description: When the status was last checked.
|
||||
format: date-time
|
||||
@@ -73,8 +193,13 @@ spec:
|
||||
reason:
|
||||
description: Reason for the current status.
|
||||
enum:
|
||||
- FetchedKey
|
||||
- Listening
|
||||
- Pending
|
||||
- Disabled
|
||||
- ErrorDuringSetup
|
||||
- CouldNotFetchKey
|
||||
- CouldNotGetClusterInfo
|
||||
- FetchedKey
|
||||
type: string
|
||||
status:
|
||||
description: Status of the attempted integration strategy.
|
||||
@@ -86,6 +211,7 @@ spec:
|
||||
description: Type of integration attempted.
|
||||
enum:
|
||||
- KubeClusterSigningCertificate
|
||||
- ImpersonationProxy
|
||||
type: string
|
||||
required:
|
||||
- lastUpdateTime
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("@ytt:json", "json")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel", "pinnipedDevAPIGroupWithPrefix")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "deploymentPodLabel", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel", "pinnipedDevAPIGroupWithPrefix")
|
||||
#@ load("@ytt:template", "template")
|
||||
|
||||
#@ if not data.values.into_namespace:
|
||||
---
|
||||
@@ -22,6 +23,25 @@ metadata:
|
||||
labels: #@ labels()
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("kube-cert-agent")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("impersonation-proxy")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
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")
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("config")
|
||||
@@ -42,6 +62,12 @@ data:
|
||||
servingCertificateSecret: (@= defaultResourceNameWithSuffix("api-tls-serving-certificate") @)
|
||||
credentialIssuer: (@= defaultResourceNameWithSuffix("config") @)
|
||||
apiService: (@= defaultResourceNameWithSuffix("api") @)
|
||||
impersonationLoadBalancerService: (@= defaultResourceNameWithSuffix("impersonation-proxy-load-balancer") @)
|
||||
impersonationClusterIPService: (@= defaultResourceNameWithSuffix("impersonation-proxy-cluster-ip") @)
|
||||
impersonationTLSCertificateSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-tls-serving-certificate") @)
|
||||
impersonationCACertificateSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-ca-certificate") @)
|
||||
impersonationSignerSecret: (@= defaultResourceNameWithSuffix("impersonation-proxy-signer-ca-certificate") @)
|
||||
agentServiceAccount: (@= defaultResourceNameWithSuffix("kube-cert-agent") @)
|
||||
labels: (@= json.encode(labels()).rstrip() @)
|
||||
kubeCertAgent:
|
||||
namePrefix: (@= defaultResourceNameWithSuffix("kube-cert-agent-") @)
|
||||
@@ -83,10 +109,16 @@ metadata:
|
||||
spec:
|
||||
replicas: #@ data.values.replicas
|
||||
selector:
|
||||
#! In hindsight, this should have been deploymentPodLabel(), but this field is immutable so changing it would break upgrades.
|
||||
matchLabels: #@ defaultLabel()
|
||||
template:
|
||||
metadata:
|
||||
labels: #@ defaultLabel()
|
||||
labels:
|
||||
#! This has always included defaultLabel(), which is used by this Deployment's selector.
|
||||
_: #@ template.replace(defaultLabel())
|
||||
#! More recently added the more unique deploymentPodLabel() so Services can select these Pods more specifically
|
||||
#! without accidentally selecting any other Deployment's Pods, especially the kube cert agent Deployment's Pods.
|
||||
_: #@ template.replace(deploymentPodLabel())
|
||||
annotations:
|
||||
scheduler.alpha.kubernetes.io/critical-pod: ""
|
||||
spec:
|
||||
@@ -106,6 +138,8 @@ spec:
|
||||
image: #@ data.values.image_repo + ":" + data.values.image_tag
|
||||
#@ end
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
@@ -113,14 +147,31 @@ spec:
|
||||
limits:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
args:
|
||||
command:
|
||||
- pinniped-concierge
|
||||
- --config=/etc/config/pinniped.yaml
|
||||
- --downward-api-path=/etc/podinfo
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: config-volume
|
||||
mountPath: /etc/config
|
||||
readOnly: true
|
||||
- name: podinfo
|
||||
mountPath: /etc/podinfo
|
||||
readOnly: true
|
||||
- name: impersonation-proxy
|
||||
mountPath: /var/run/secrets/impersonation-proxy.concierge.pinniped.dev/serviceaccount
|
||||
readOnly: true
|
||||
env:
|
||||
#@ if data.values.https_proxy:
|
||||
- name: HTTPS_PROXY
|
||||
value: #@ data.values.https_proxy
|
||||
#@ end
|
||||
#@ if data.values.https_proxy and data.values.no_proxy:
|
||||
- name: NO_PROXY
|
||||
value: #@ data.values.no_proxy
|
||||
#@ end
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
@@ -140,9 +191,19 @@ spec:
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 100Mi
|
||||
- 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:
|
||||
@@ -171,7 +232,7 @@ spec:
|
||||
- weight: 50
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels: #@ defaultLabel()
|
||||
matchLabels: #@ deploymentPodLabel()
|
||||
topologyKey: kubernetes.io/hostname
|
||||
---
|
||||
apiVersion: v1
|
||||
@@ -181,14 +242,34 @@ metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("api")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector: #@ defaultLabel()
|
||||
selector: #@ deploymentPodLabel()
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
targetPort: 8443
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("proxy")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector: #@ deploymentPodLabel()
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
targetPort: 8444
|
||||
---
|
||||
apiVersion: apiregistration.k8s.io/v1
|
||||
kind: APIService
|
||||
metadata:
|
||||
@@ -197,10 +278,57 @@ metadata:
|
||||
spec:
|
||||
version: v1alpha1
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("login.concierge")
|
||||
groupPriorityMinimum: 2500
|
||||
versionPriority: 10
|
||||
groupPriorityMinimum: 9900
|
||||
versionPriority: 15
|
||||
#! caBundle: Do not include this key here. Starts out null, will be updated/owned by the golang code.
|
||||
service:
|
||||
name: #@ defaultResourceNameWithSuffix("api")
|
||||
namespace: #@ namespace()
|
||||
port: 443
|
||||
---
|
||||
apiVersion: apiregistration.k8s.io/v1
|
||||
kind: APIService
|
||||
metadata:
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("v1alpha1.identity.concierge")
|
||||
labels: #@ labels()
|
||||
spec:
|
||||
version: v1alpha1
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("identity.concierge")
|
||||
groupPriorityMinimum: 9900
|
||||
versionPriority: 15
|
||||
#! caBundle: Do not include this key here. Starts out null, will be updated/owned by the golang code.
|
||||
service:
|
||||
name: #@ defaultResourceNameWithSuffix("api")
|
||||
namespace: #@ namespace()
|
||||
port: 443
|
||||
---
|
||||
apiVersion: #@ pinnipedDevAPIGroupWithPrefix("config.concierge") + "/v1alpha1"
|
||||
kind: CredentialIssuer
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("config")
|
||||
labels: #@ labels()
|
||||
spec:
|
||||
impersonationProxy:
|
||||
mode: #@ data.values.impersonation_proxy_spec.mode
|
||||
#@ if data.values.impersonation_proxy_spec.external_endpoint:
|
||||
externalEndpoint: #@ data.values.impersonation_proxy_spec.external_endpoint
|
||||
#@ end
|
||||
service:
|
||||
type: #@ data.values.impersonation_proxy_spec.service.type
|
||||
#@ if data.values.impersonation_proxy_spec.service.load_balancer_ip:
|
||||
loadBalancerIP: #@ data.values.impersonation_proxy_spec.service.load_balancer_ip
|
||||
#@ end
|
||||
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
|
||||
|
||||
@@ -25,9 +25,14 @@
|
||||
#@ end
|
||||
|
||||
#@ def defaultLabel():
|
||||
#! Note that the name of this label's key is also assumed by kubecertagent.go and impersonator_config.go
|
||||
app: #@ data.values.app_name
|
||||
#@ end
|
||||
|
||||
#@ def deploymentPodLabel():
|
||||
deployment.pinniped.dev: concierge
|
||||
#@ end
|
||||
|
||||
#@ def labels():
|
||||
_: #@ template.replace(defaultLabel())
|
||||
_: #@ template.replace(data.values.custom_labels)
|
||||
|
||||
@@ -24,13 +24,13 @@ rules:
|
||||
- apiGroups: [ flowcontrol.apiserver.k8s.io ]
|
||||
resources: [ flowschemas, prioritylevelconfigurations ]
|
||||
verbs: [ get, list, watch ]
|
||||
- apiGroups: [ policy ]
|
||||
resources: [ podsecuritypolicies ]
|
||||
verbs: [ use ]
|
||||
- apiGroups: [ security.openshift.io ]
|
||||
resources: [ securitycontextconstraints ]
|
||||
verbs: [ use ]
|
||||
resourceNames: [ nonroot ]
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ nodes ]
|
||||
verbs: [ list ]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("config.concierge")
|
||||
resources: [ credentialissuers ]
|
||||
@@ -38,7 +38,7 @@ rules:
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("config.concierge")
|
||||
resources: [ credentialissuers/status ]
|
||||
verbs: [get, patch, update]
|
||||
verbs: [ get, patch, update ]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
|
||||
resources: [ jwtauthenticators, webhookauthenticators ]
|
||||
@@ -58,6 +58,63 @@ roleRef:
|
||||
name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
#! Give minimal permissions to impersonation proxy service account
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("impersonation-proxy")
|
||||
labels: #@ labels()
|
||||
rules:
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "users", "groups", "serviceaccounts" ]
|
||||
verbs: [ "impersonate" ]
|
||||
- apiGroups: [ "authentication.k8s.io" ]
|
||||
resources: [ "*" ] #! What we really want is userextras/* but the RBAC authorizer only supports */subresource, not resource/*
|
||||
verbs: [ "impersonate" ]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("impersonation-proxy")
|
||||
labels: #@ labels()
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: #@ defaultResourceNameWithSuffix("impersonation-proxy")
|
||||
namespace: #@ namespace()
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: #@ defaultResourceNameWithSuffix("impersonation-proxy")
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
#! Give permission to the kube-cert-agent Pod to run privileged.
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("kube-cert-agent")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
rules:
|
||||
- apiGroups: [ policy ]
|
||||
resources: [ podsecuritypolicies ]
|
||||
verbs: [ use ]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("kube-cert-agent")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: #@ defaultResourceNameWithSuffix("kube-cert-agent")
|
||||
namespace: #@ namespace()
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: #@ defaultResourceNameWithSuffix("kube-cert-agent")
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
#! Give permission to various objects within the app's own namespace
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -69,21 +126,36 @@ metadata:
|
||||
rules:
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ services ]
|
||||
verbs: [ create, get, list, patch, update, watch ]
|
||||
verbs: [ create, get, list, patch, update, watch, delete ]
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ secrets ]
|
||||
verbs: [ create, get, list, patch, update, watch, delete ]
|
||||
#! We need to be able to CRUD pods in our namespace so we can reconcile the kube-cert-agent pods.
|
||||
#! We need to be able to watch pods in our namespace so we can find the kube-cert-agent pods.
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ pods ]
|
||||
verbs: [ create, get, list, patch, update, watch, delete ]
|
||||
verbs: [ get, list, watch ]
|
||||
#! We need to be able to exec into pods in our namespace so we can grab the API server's private key
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ pods/exec ]
|
||||
verbs: [ create ]
|
||||
- apiGroups: [apps]
|
||||
resources: [replicasets,deployments]
|
||||
verbs: [get]
|
||||
#! We need to be able to delete pods in our namespace so we can clean up legacy kube-cert-agent pods.
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ pods ]
|
||||
verbs: [ delete ]
|
||||
#! We need to be able to create and update deployments in our namespace so we can manage the kube-cert-agent Deployment.
|
||||
- apiGroups: [ apps ]
|
||||
resources: [ deployments ]
|
||||
verbs: [ create, get, list, patch, update, watch, delete ]
|
||||
#! We need to be able to get replicasets so we can form the correct owner references on our generated objects.
|
||||
- apiGroups: [ apps ]
|
||||
resources: [ replicasets ]
|
||||
verbs: [ get ]
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ configmaps ]
|
||||
verbs: [ list, get, watch ]
|
||||
- apiGroups: [ coordination.k8s.io ]
|
||||
resources: [ leases ]
|
||||
verbs: [ create, get, update ]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -133,18 +205,22 @@ roleRef:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
|
||||
name: #@ defaultResourceNameWithSuffix("pre-authn-apis")
|
||||
labels: #@ labels()
|
||||
rules:
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("login.concierge")
|
||||
resources: [ tokencredentialrequests ]
|
||||
verbs: [ create ]
|
||||
verbs: [ create, list ]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("identity.concierge")
|
||||
resources: [ whoamirequests ]
|
||||
verbs: [ create, list ]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
|
||||
name: #@ defaultResourceNameWithSuffix("pre-authn-apis")
|
||||
labels: #@ labels()
|
||||
subjects:
|
||||
- kind: Group
|
||||
@@ -155,7 +231,7 @@ subjects:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
|
||||
name: #@ defaultResourceNameWithSuffix("pre-authn-apis")
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
#! Give permissions for subjectaccessreviews, tokenreview that is needed by aggregated api servers
|
||||
|
||||
@@ -55,11 +55,49 @@ api_serving_certificate_renew_before_seconds: 2160000
|
||||
#! information), trace (timing information), all (kitchen sink).
|
||||
log_level: #! By default, when this value is left unset, only warnings and errors are printed. There is no way to suppress warning and error logs.
|
||||
|
||||
run_as_user: 1001 #! run_as_user specifies the user ID that will own the local-user-authenticator process
|
||||
run_as_group: 1001 #! run_as_group specifies the group ID that will own the local-user-authenticator process
|
||||
run_as_user: 1001 #! run_as_user specifies the user ID that will own the process
|
||||
run_as_group: 1001 #! run_as_group specifies the group ID that will own the process
|
||||
|
||||
#! Specify the API group suffix for all Pinniped API groups. By default, this is set to
|
||||
#! pinniped.dev, so Pinniped API groups will look like foo.pinniped.dev,
|
||||
#! authentication.concierge.pinniped.dev, etc. As an example, if this is set to tuna.io, then
|
||||
#! Pinniped API groups will look like foo.tuna.io. authentication.concierge.tuna.io, etc.
|
||||
api_group_suffix: pinniped.dev
|
||||
|
||||
#! 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
|
||||
|
||||
@@ -63,8 +63,8 @@ spec:
|
||||
image: #@ data.values.image_repo + ":" + data.values.image_tag
|
||||
#@ end
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: #! override the default entrypoint
|
||||
- /usr/local/bin/local-user-authenticator
|
||||
command:
|
||||
- local-user-authenticator
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -73,6 +73,9 @@ metadata:
|
||||
namespace: local-user-authenticator
|
||||
labels:
|
||||
app: local-user-authenticator
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
|
||||
@@ -15,5 +15,5 @@ image_tag: latest
|
||||
#! Optional.
|
||||
image_pull_dockerconfigjson: #! e.g. {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}
|
||||
|
||||
run_as_user: 1001 #! run_as_user specifies the user ID that will own the local-user-authenticator process
|
||||
run_as_group: 1001 #! run_as_group specifies the group ID that will own the local-user-authenticator process
|
||||
run_as_user: 1001 #! run_as_user specifies the user ID that will own the process
|
||||
run_as_group: 1001 #! run_as_group specifies the group ID that will own the process
|
||||
|
||||
@@ -1,184 +1,3 @@
|
||||
# Deploying the Pinniped Supervisor
|
||||
# Pinniped Supervisor Deployment
|
||||
|
||||
## What is the Pinniped Supervisor?
|
||||
|
||||
The Pinniped Supervisor app is a component of the Pinniped OIDC and Cluster Federation solutions.
|
||||
It can be deployed when those features are needed.
|
||||
|
||||
## Installing the Latest Version with Default Options
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-supervisor.yaml
|
||||
```
|
||||
|
||||
## Installing a Specific Version with Default Options
|
||||
|
||||
Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number
|
||||
and use it to replace the version number in the URL below.
|
||||
|
||||
```bash
|
||||
# Replace v0.4.1 with your preferred version in the URL below
|
||||
kubectl apply -f https://get.pinniped.dev/v0.4.1/install-pinniped-supervisor.yaml
|
||||
```
|
||||
|
||||
## Installing with Custom Options
|
||||
|
||||
Creating your own deployment YAML file requires `ytt` from [Carvel](https://carvel.dev/) to template the YAML files
|
||||
in the `deploy/supervisor` directory.
|
||||
Either [install `ytt`](https://get-ytt.io/) or use the [container image from Dockerhub](https://hub.docker.com/r/k14s/image/tags).
|
||||
|
||||
1. `git clone` this repo and `git checkout` the release version tag of the release that you would like to deploy.
|
||||
1. The configuration options are in [deploy/supervisor/values.yml](values.yaml).
|
||||
Fill in the values in that file, or override those values using additional `ytt` command-line options in
|
||||
the command below. Use the release version tag as the `image_tag` value.
|
||||
2. In a terminal, cd to this `deploy/supervisor` directory
|
||||
3. To generate the final YAML files, run `ytt --file .`
|
||||
4. Deploy the generated YAML using your preferred deployment tool, such as `kubectl` or [`kapp`](https://get-kapp.io/).
|
||||
For example: `ytt --file . | kapp deploy --yes --app pinniped-supervisor --diff-changes --file -`
|
||||
|
||||
## Configuring After Installing
|
||||
|
||||
### Exposing the Supervisor App as a Service
|
||||
|
||||
The Supervisor app's endpoints should be exposed as HTTPS endpoints with proper TLS certificates signed by a
|
||||
Certificate Authority which will be trusted by your user's web browsers. Because there are
|
||||
many ways to expose TLS services from a Kubernetes cluster, the Supervisor app leaves this up to the user.
|
||||
The most common ways are:
|
||||
|
||||
1. Define an [`Ingress` resource](https://kubernetes.io/docs/concepts/services-networking/ingress/) with TLS certificates.
|
||||
In this case, the ingress will terminate TLS. Typically, the ingress will then talk plain HTTP to its backend,
|
||||
which would be a NodePort or LoadBalancer Service in front of the HTTP port 8080 of the Supervisor pods.
|
||||
|
||||
The required configuration of the Ingress is specific to your cluster's Ingress Controller, so please refer to the
|
||||
documentation from your Kubernetes provider. If you are using a cluster from a cloud provider, then you'll probably
|
||||
want to start with that provider's documentation. For example, if your cluster is a Google GKE cluster, refer to
|
||||
the [GKE documentation for Ingress](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress).
|
||||
Otherwise, the Kubernetes documentation provides a list of popular
|
||||
[Ingress Controllers](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/), including
|
||||
[Contour](https://projectcontour.io/) and many others.
|
||||
|
||||
1. Or, define a [TCP LoadBalancer Service](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer)
|
||||
which is a layer 4 load balancer and does not terminate TLS. In this case, the Supervisor app will need to be
|
||||
configured with TLS certificates and will terminate the TLS connection itself (see the section about FederationDomain
|
||||
below). The LoadBalancer Service should be configured to use the HTTPS port 443 of the Supervisor pods as its `targetPort`.
|
||||
|
||||
*Warning:* Do not expose the Supervisor's port 8080 to the public. It would not be secure for the OIDC protocol
|
||||
to use HTTP, because the user's secret OIDC tokens would be transmitted across the network without encryption.
|
||||
|
||||
1. Or, expose the Supervisor app using a Kubernetes service mesh technology, e.g. [Istio](https://istio.io/).
|
||||
Please see the documentation for your service mesh. Generally, the setup would be similar to the description
|
||||
above for defining an ingress, expect the service mesh would probably provide both the ingress with TLS termination
|
||||
and the service.
|
||||
|
||||
For either of the first two options mentioned above, if you installed using `ytt` then you can use
|
||||
the related `service_*` options from [deploy/supervisor/values.yml](values.yaml) to create a Service.
|
||||
If you installed using `install-supervisor.yaml` then you can create
|
||||
the Service separately after installing the Supervisor app. There is no `Ingress` included in the `ytt` templates,
|
||||
so if you choose to use an Ingress then you'll need to create that separately after installing the Supervisor app.
|
||||
|
||||
#### Example: Using a LoadBalancer Service
|
||||
|
||||
This is an example of creating a LoadBalancer Service to expose port 8443 of the Supervisor app outside the cluster.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: pinniped-supervisor-loadbalancer
|
||||
# Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
|
||||
namespace: pinniped-supervisor
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
# Assuming that this is how the supervisor pods are labeled. This is the default in install-supervisor.yaml.
|
||||
app: pinniped-supervisor
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
targetPort: 8443
|
||||
```
|
||||
|
||||
#### Example: Using a NodePort Service
|
||||
|
||||
A NodePort Service exposes the app as a port on the nodes of the cluster.
|
||||
|
||||
This is convenient for use with kind clusters, because kind can
|
||||
[expose node ports as localhost ports on the host machine](https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings)
|
||||
without requiring an Ingress, although
|
||||
[kind also supports several Ingress Controllers](https://kind.sigs.k8s.io/docs/user/ingress).
|
||||
|
||||
A NodePort Service could also be used behind an Ingress which is terminating TLS.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: pinniped-supervisor-nodeport
|
||||
# Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
|
||||
namespace: pinniped-supervisor
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
# Assuming that this is how the supervisor pods are labeled. This is the default in install-supervisor.yaml.
|
||||
app: pinniped-supervisor
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
nodePort: 31234 # This is the port that you would forward to the kind host. Or omit this key for a random port.
|
||||
```
|
||||
|
||||
### Configuring the Supervisor to Act as an OIDC Provider
|
||||
|
||||
The Supervisor can be configured as an OIDC provider by creating `FederationDomain` resources
|
||||
in the same namespace where the Supervisor app was installed. For example:
|
||||
|
||||
```yaml
|
||||
apiVersion: config.supervisor.pinniped.dev/v1alpha1
|
||||
kind: FederationDomain
|
||||
metadata:
|
||||
name: my-provider
|
||||
# Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
|
||||
namespace: pinniped-supervisor
|
||||
spec:
|
||||
# The hostname would typically match the DNS name of the public ingress or load balancer for the cluster.
|
||||
# Any path can be specified, which allows a single hostname to have multiple different issuers. The path is optional.
|
||||
issuer: https://my-issuer.example.com/any/path
|
||||
|
||||
# Optionally configure the 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 OIDC Provider.
|
||||
tls:
|
||||
secretName: my-tls-cert-secret
|
||||
```
|
||||
|
||||
#### Configuring TLS for the Supervisor OIDC Endpoints
|
||||
|
||||
If you have terminated TLS outside the app, for example using an Ingress with TLS certificates, then you do not need to
|
||||
configure TLS certificates on the FederationDomain.
|
||||
|
||||
If you are using a LoadBalancer Service to expose the Supervisor app outside your cluster, then you will
|
||||
also need to configure the Supervisor app to terminate TLS. There are two places to configure TLS certificates:
|
||||
|
||||
1. Each `FederationDomain` can be configured with TLS certificates, using the `spec.tls.secretName` field.
|
||||
|
||||
1. The default TLS certificate for all OIDC providers can be configured by creating a Secret called
|
||||
`pinniped-supervisor-default-tls-certificate` in the same namespace in which the Supervisor was installed.
|
||||
|
||||
The default TLS certificate will be used for all OIDC providers which did not declare a `spec.tls.secretName`.
|
||||
Also, the `spec.tls.secretName` will be ignored for incoming requests to the OIDC endpoints
|
||||
that use an IP address as the host, so those requests will always present the default TLS certificates
|
||||
to the client. When the request includes the hostname, and that hostname matches the hostname of an `Issuer`,
|
||||
then the TLS certificate defined by the `spec.tls.secretName` will be used. If that issuer did not
|
||||
define `spec.tls.secretName` then the default TLS certificate will be used. If neither exists,
|
||||
then the client will get a TLS error because the server will not present any TLS certificate.
|
||||
|
||||
It is recommended that you have a DNS entry for your load balancer or Ingress, and that you configure the
|
||||
OIDC provider's `Issuer` using that DNS hostname, and that the TLS certificate for that provider also
|
||||
covers that same hostname.
|
||||
|
||||
You can create the certificate Secrets however you like, for example you could use [cert-manager](https://cert-manager.io/)
|
||||
or `kubectl create secret tls`.
|
||||
Keep in mind that your users will load some of these endpoints in their web browsers, so the TLS certificates
|
||||
should be signed by a Certificate Authority that will be trusted by their browsers.
|
||||
See [the how-to guide for details](https://pinniped.dev/docs/howto/install-supervisor/).
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("@ytt:json", "json")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "deploymentPodLabel", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel")
|
||||
#@ load("@ytt:template", "template")
|
||||
|
||||
#@ if not data.values.into_namespace:
|
||||
---
|
||||
@@ -59,10 +60,16 @@ metadata:
|
||||
spec:
|
||||
replicas: #@ data.values.replicas
|
||||
selector:
|
||||
#! In hindsight, this should have been deploymentPodLabel(), but this field is immutable so changing it would break upgrades.
|
||||
matchLabels: #@ defaultLabel()
|
||||
template:
|
||||
metadata:
|
||||
labels: #@ defaultLabel()
|
||||
labels:
|
||||
#! This has always included defaultLabel(), which is used by this Deployment's selector.
|
||||
_: #@ template.replace(defaultLabel())
|
||||
#! More recently added the more unique deploymentPodLabel() so Services can select these Pods more specifically
|
||||
#! without accidentally selecting pods from any future Deployments which might also want to use the defaultLabel().
|
||||
_: #@ template.replace(deploymentPodLabel())
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: #@ data.values.run_as_user
|
||||
@@ -80,11 +87,12 @@ spec:
|
||||
image: #@ data.values.image_repo + ":" + data.values.image_tag
|
||||
#@ end
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: #! override the default entrypoint
|
||||
- /usr/local/bin/pinniped-supervisor
|
||||
args:
|
||||
command:
|
||||
- pinniped-supervisor
|
||||
- /etc/podinfo
|
||||
- /etc/config/pinniped.yaml
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
@@ -95,13 +103,24 @@ spec:
|
||||
volumeMounts:
|
||||
- name: config-volume
|
||||
mountPath: /etc/config
|
||||
readOnly: true
|
||||
- name: podinfo
|
||||
mountPath: /etc/podinfo
|
||||
readOnly: true
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
- containerPort: 8443
|
||||
protocol: TCP
|
||||
env:
|
||||
#@ if data.values.https_proxy:
|
||||
- name: HTTPS_PROXY
|
||||
value: #@ data.values.https_proxy
|
||||
#@ end
|
||||
#@ if data.values.https_proxy and data.values.no_proxy:
|
||||
- name: NO_PROXY
|
||||
value: #@ data.values.no_proxy
|
||||
#@ end
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
@@ -144,5 +163,5 @@ spec:
|
||||
- weight: 50
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels: #@ defaultLabel()
|
||||
matchLabels: #@ deploymentPodLabel()
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
app: #@ data.values.app_name
|
||||
#@ end
|
||||
|
||||
#@ def deploymentPodLabel():
|
||||
deployment.pinniped.dev: supervisor
|
||||
#@ end
|
||||
|
||||
#@ def labels():
|
||||
_: #@ template.replace(defaultLabel())
|
||||
_: #@ template.replace(data.values.custom_labels)
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.4.0
|
||||
creationTimestamp: null
|
||||
name: activedirectoryidentityproviders.idp.supervisor.pinniped.dev
|
||||
spec:
|
||||
group: idp.supervisor.pinniped.dev
|
||||
names:
|
||||
categories:
|
||||
- pinniped
|
||||
- pinniped-idp
|
||||
- pinniped-idps
|
||||
kind: ActiveDirectoryIdentityProvider
|
||||
listKind: ActiveDirectoryIdentityProviderList
|
||||
plural: activedirectoryidentityproviders
|
||||
singular: activedirectoryidentityprovider
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.host
|
||||
name: Host
|
||||
type: string
|
||||
- jsonPath: .status.phase
|
||||
name: Status
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ActiveDirectoryIdentityProvider describes the configuration of
|
||||
an upstream Microsoft Active Directory identity provider.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: Spec for configuring the identity provider.
|
||||
properties:
|
||||
bind:
|
||||
description: Bind contains the configuration for how to provide access
|
||||
credentials during an initial bind to the ActiveDirectory server
|
||||
to be allowed to perform searches and binds to validate a user's
|
||||
credentials during a user's authentication attempt.
|
||||
properties:
|
||||
secretName:
|
||||
description: SecretName contains the name of a namespace-local
|
||||
Secret object that provides the username and password for an
|
||||
Active Directory bind user. This account will be used to perform
|
||||
LDAP searches. The Secret should be of type "kubernetes.io/basic-auth"
|
||||
which includes "username" and "password" keys. The username
|
||||
value should be the full dn (distinguished name) of your bind
|
||||
account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
|
||||
The password must be non-empty.
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
type: object
|
||||
groupSearch:
|
||||
description: GroupSearch contains the configuration for searching
|
||||
for a user's group membership in ActiveDirectory.
|
||||
properties:
|
||||
attributes:
|
||||
description: Attributes specifies how the group's information
|
||||
should be read from each ActiveDirectory entry which was found
|
||||
as the result of the group search.
|
||||
properties:
|
||||
groupName:
|
||||
description: GroupName specifies the name of the attribute
|
||||
in the Active Directory entries whose value shall become
|
||||
a group name in the user's list of groups after a successful
|
||||
authentication. The value of this field is case-sensitive
|
||||
and must match the case of the attribute name returned by
|
||||
the ActiveDirectory server in the user's entry. E.g. "cn"
|
||||
for common name. Distinguished names can be used by specifying
|
||||
lower-case "dn". Optional. When not specified, this defaults
|
||||
to a custom field that looks like "sAMAccountName@domain",
|
||||
where domain is constructed from the domain components of
|
||||
the group DN.
|
||||
type: string
|
||||
type: object
|
||||
base:
|
||||
description: Base is the dn (distinguished name) that should be
|
||||
used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com".
|
||||
Optional, when not specified it will be based on the result
|
||||
of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
The default behavior searches your entire domain for groups.
|
||||
It may make sense to specify a subtree as a search base if you
|
||||
wish to exclude some groups for security reasons or to make
|
||||
searches faster.
|
||||
type: string
|
||||
filter:
|
||||
description: Filter is the ActiveDirectory search filter which
|
||||
should be applied when searching for groups for a user. The
|
||||
pattern "{}" must occur in the filter at least once and will
|
||||
be dynamically replaced by the dn (distinguished name) of the
|
||||
user entry found as a result of the user search. E.g. "member={}"
|
||||
or "&(objectClass=groupOfNames)(member={})". For more information
|
||||
about ActiveDirectory filters, see https://ldap.com/ldap-filters.
|
||||
Note that the dn (distinguished name) is not an attribute of
|
||||
an entry, so "dn={}" cannot be used. Optional. When not specified,
|
||||
the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})".
|
||||
This searches nested groups by default. Note that nested group
|
||||
search can be slow for some Active Directory servers. To disable
|
||||
it, you can set the filter to "(&(objectClass=group)(member={})"
|
||||
type: string
|
||||
type: object
|
||||
host:
|
||||
description: 'Host is the hostname of this Active Directory identity
|
||||
provider, i.e., where to connect. For example: ldap.example.com:636.'
|
||||
minLength: 1
|
||||
type: string
|
||||
tls:
|
||||
description: TLS contains the connection settings for how to establish
|
||||
the connection to the Host.
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: X.509 Certificate Authority (base64-encoded PEM bundle).
|
||||
If omitted, a default set of system roots will be trusted.
|
||||
type: string
|
||||
type: object
|
||||
userSearch:
|
||||
description: UserSearch contains the configuration for searching for
|
||||
a user by name in Active Directory.
|
||||
properties:
|
||||
attributes:
|
||||
description: Attributes specifies how the user's information should
|
||||
be read from the ActiveDirectory entry which was found as the
|
||||
result of the user search.
|
||||
properties:
|
||||
uid:
|
||||
description: UID specifies the name of the attribute in the
|
||||
ActiveDirectory entry which whose value shall be used to
|
||||
uniquely identify the user within this ActiveDirectory provider
|
||||
after a successful authentication. Optional, when empty
|
||||
this defaults to "objectGUID".
|
||||
type: string
|
||||
username:
|
||||
description: Username specifies the name of the attribute
|
||||
in Active Directory entry whose value shall become the username
|
||||
of the user after a successful authentication. Optional,
|
||||
when empty this defaults to "userPrincipalName".
|
||||
type: string
|
||||
type: object
|
||||
base:
|
||||
description: Base is the dn (distinguished name) that should be
|
||||
used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
|
||||
Optional, when not specified it will be based on the result
|
||||
of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
The default behavior searches your entire domain for users.
|
||||
It may make sense to specify a subtree as a search base if you
|
||||
wish to exclude some users or to make searches faster.
|
||||
type: string
|
||||
filter:
|
||||
description: Filter is the search filter which should be applied
|
||||
when searching for users. The pattern "{}" must occur in the
|
||||
filter at least once and will be dynamically replaced by the
|
||||
username for which the search is being run. E.g. "mail={}" or
|
||||
"&(objectClass=person)(uid={})". For more information about
|
||||
LDAP filters, see https://ldap.com/ldap-filters. Note that the
|
||||
dn (distinguished name) is not an attribute of an entry, so
|
||||
"dn={}" cannot be used. Optional. When not specified, the default
|
||||
will be '(&(objectClass=person)(!(objectClass=computer))(!(showInAdvancedViewOnly=TRUE))(|(sAMAccountName={}")(mail={})(userPrincipalName={})(sAMAccountType=805306368))'
|
||||
This means that the user is a person, is not a computer, the
|
||||
sAMAccountType is for a normal user account, and is not shown
|
||||
in advanced view only (which would likely mean its a system
|
||||
created service account with advanced permissions). Also, either
|
||||
the sAMAccountName, the userPrincipalName, or the mail attribute
|
||||
matches the input username.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- host
|
||||
type: object
|
||||
status:
|
||||
description: Status of the identity provider.
|
||||
properties:
|
||||
conditions:
|
||||
description: Represents the observations of an identity provider's
|
||||
current state.
|
||||
items:
|
||||
description: Condition status of a resource (mirrored from the metav1.Condition
|
||||
type added in Kubernetes 1.19). In a future API version we can
|
||||
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the ActiveDirectoryIdentityProvider.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -0,0 +1,278 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.4.0
|
||||
creationTimestamp: null
|
||||
name: ldapidentityproviders.idp.supervisor.pinniped.dev
|
||||
spec:
|
||||
group: idp.supervisor.pinniped.dev
|
||||
names:
|
||||
categories:
|
||||
- pinniped
|
||||
- pinniped-idp
|
||||
- pinniped-idps
|
||||
kind: LDAPIdentityProvider
|
||||
listKind: LDAPIdentityProviderList
|
||||
plural: ldapidentityproviders
|
||||
singular: ldapidentityprovider
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.host
|
||||
name: Host
|
||||
type: string
|
||||
- jsonPath: .status.phase
|
||||
name: Status
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: LDAPIdentityProvider describes the configuration of an upstream
|
||||
Lightweight Directory Access Protocol (LDAP) identity provider.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: Spec for configuring the identity provider.
|
||||
properties:
|
||||
bind:
|
||||
description: Bind contains the configuration for how to provide access
|
||||
credentials during an initial bind to the LDAP server to be allowed
|
||||
to perform searches and binds to validate a user's credentials during
|
||||
a user's authentication attempt.
|
||||
properties:
|
||||
secretName:
|
||||
description: SecretName contains the name of a namespace-local
|
||||
Secret object that provides the username and password for an
|
||||
LDAP bind user. This account will be used to perform LDAP searches.
|
||||
The Secret should be of type "kubernetes.io/basic-auth" which
|
||||
includes "username" and "password" keys. The username value
|
||||
should be the full dn (distinguished name) of your bind account,
|
||||
e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password
|
||||
must be non-empty.
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
type: object
|
||||
groupSearch:
|
||||
description: GroupSearch contains the configuration for searching
|
||||
for a user's group membership in the LDAP provider.
|
||||
properties:
|
||||
attributes:
|
||||
description: Attributes specifies how the group's information
|
||||
should be read from each LDAP entry which was found as the result
|
||||
of the group search.
|
||||
properties:
|
||||
groupName:
|
||||
description: GroupName specifies the name of the attribute
|
||||
in the LDAP entries whose value shall become a group name
|
||||
in the user's list of groups after a successful authentication.
|
||||
The value of this field is case-sensitive and must match
|
||||
the case of the attribute name returned by the LDAP server
|
||||
in the user's entry. E.g. "cn" for common name. Distinguished
|
||||
names can be used by specifying lower-case "dn". Optional.
|
||||
When not specified, the default will act as if the GroupName
|
||||
were specified as "dn" (distinguished name).
|
||||
type: string
|
||||
type: object
|
||||
base:
|
||||
description: Base is the dn (distinguished name) that should be
|
||||
used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com".
|
||||
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.
|
||||
type: string
|
||||
filter:
|
||||
description: Filter is the LDAP search filter which should be
|
||||
applied when searching for groups for a user. The pattern "{}"
|
||||
must occur in the filter at least once and will be dynamically
|
||||
replaced by the dn (distinguished name) of the user entry found
|
||||
as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})".
|
||||
For more information about LDAP filters, see https://ldap.com/ldap-filters.
|
||||
Note that the dn (distinguished name) is not an attribute of
|
||||
an entry, so "dn={}" cannot be used. Optional. When not specified,
|
||||
the default will act as if the Filter were specified as "member={}".
|
||||
type: string
|
||||
type: object
|
||||
host:
|
||||
description: 'Host is the hostname of this LDAP identity provider,
|
||||
i.e., where to connect. For example: ldap.example.com:636.'
|
||||
minLength: 1
|
||||
type: string
|
||||
tls:
|
||||
description: TLS contains the connection settings for how to establish
|
||||
the connection to the Host.
|
||||
properties:
|
||||
certificateAuthorityData:
|
||||
description: X.509 Certificate Authority (base64-encoded PEM bundle).
|
||||
If omitted, a default set of system roots will be trusted.
|
||||
type: string
|
||||
type: object
|
||||
userSearch:
|
||||
description: UserSearch contains the configuration for searching for
|
||||
a user by name in the LDAP provider.
|
||||
properties:
|
||||
attributes:
|
||||
description: Attributes specifies how the user's information should
|
||||
be read from the LDAP entry which was found as the result of
|
||||
the user search.
|
||||
properties:
|
||||
uid:
|
||||
description: UID specifies the name of the attribute in the
|
||||
LDAP entry which whose value shall be used to uniquely identify
|
||||
the user within this LDAP provider after a successful authentication.
|
||||
E.g. "uidNumber" or "objectGUID". The value of this field
|
||||
is case-sensitive and must match the case of the attribute
|
||||
name returned by the LDAP server in the user's entry. Distinguished
|
||||
names can be used by specifying lower-case "dn".
|
||||
minLength: 1
|
||||
type: string
|
||||
username:
|
||||
description: Username specifies the name of the attribute
|
||||
in the LDAP entry whose value shall become the username
|
||||
of the user after a successful authentication. This would
|
||||
typically be the same attribute name used in the user search
|
||||
filter, although it can be different. E.g. "mail" or "uid"
|
||||
or "userPrincipalName". The value of this field is case-sensitive
|
||||
and must match the case of the attribute name returned by
|
||||
the LDAP server in the user's entry. Distinguished names
|
||||
can be used by specifying lower-case "dn". When this field
|
||||
is set to "dn" then the LDAPIdentityProviderUserSearch's
|
||||
Filter field cannot be blank, since the default value of
|
||||
"dn={}" would not work.
|
||||
minLength: 1
|
||||
type: string
|
||||
type: object
|
||||
base:
|
||||
description: Base is the dn (distinguished name) that should be
|
||||
used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
|
||||
minLength: 1
|
||||
type: string
|
||||
filter:
|
||||
description: Filter is the LDAP search filter which should be
|
||||
applied when searching for users. The pattern "{}" must occur
|
||||
in the filter at least once and will be dynamically replaced
|
||||
by the username for which the search is being run. E.g. "mail={}"
|
||||
or "&(objectClass=person)(uid={})". For more information about
|
||||
LDAP filters, see https://ldap.com/ldap-filters. Note that the
|
||||
dn (distinguished name) is not an attribute of an entry, so
|
||||
"dn={}" cannot be used. Optional. When not specified, the default
|
||||
will act as if the Filter were specified as the value from Attributes.Username
|
||||
appended by "={}". When the Attributes.Username is set to "dn"
|
||||
then the Filter must be explicitly specified, since the default
|
||||
value of "dn={}" would not work.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- host
|
||||
type: object
|
||||
status:
|
||||
description: Status of the identity provider.
|
||||
properties:
|
||||
conditions:
|
||||
description: Represents the observations of an identity provider's
|
||||
current state.
|
||||
items:
|
||||
description: Condition status of a resource (mirrored from the metav1.Condition
|
||||
type added in Kubernetes 1.19). In a future API version we can
|
||||
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the LDAPIdentityProvider.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -59,11 +59,44 @@ spec:
|
||||
additionalScopes:
|
||||
description: AdditionalScopes are the scopes in addition to "openid"
|
||||
that will be requested as part of the authorization request
|
||||
flow with an OIDC identity provider. By default only the "openid"
|
||||
scope will be requested.
|
||||
flow with an OIDC identity provider. In the case of a Resource
|
||||
Owner Password Credentials Grant flow, AdditionalScopes are
|
||||
the scopes in addition to "openid" that will be requested as
|
||||
part of the token request (see also the allowPasswordGrant field).
|
||||
By default, only the "openid" scope will be requested.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
allowPasswordGrant:
|
||||
description: AllowPasswordGrant, when true, will allow the use
|
||||
of OAuth 2.0's Resource Owner Password Credentials Grant (see
|
||||
https://datatracker.ietf.org/doc/html/rfc6749#section-4.3) to
|
||||
authenticate to the OIDC provider using a username and password
|
||||
without a web browser, in addition to the usual browser-based
|
||||
OIDC Authorization Code Flow. The Resource Owner Password Credentials
|
||||
Grant is not officially part of the OIDC specification, so it
|
||||
may not be supported by your OIDC provider. If your OIDC provider
|
||||
supports returning ID tokens from a Resource Owner Password
|
||||
Credentials Grant token request, then you can choose to set
|
||||
this field to true. This will allow end users to choose to present
|
||||
their username and password to the kubectl CLI (using the Pinniped
|
||||
plugin) to authenticate to the cluster, without using a web
|
||||
browser to log in as is customary in OIDC Authorization Code
|
||||
Flow. This may be convenient for users, especially for identities
|
||||
from your OIDC provider which are not intended to represent
|
||||
a human actor, such as service accounts performing actions in
|
||||
a CI/CD environment. Even if your OIDC provider supports it,
|
||||
you may wish to disable this behavior by setting this field
|
||||
to false when you prefer to only allow users of this OIDCIdentityProvider
|
||||
to log in via the browser-based OIDC Authorization Code Flow.
|
||||
Using the Resource Owner Password Credentials Grant means that
|
||||
the Pinniped CLI and Pinniped Supervisor will directly handle
|
||||
your end users' passwords (similar to LDAPIdentityProvider),
|
||||
and you will not be able to require multi-factor authentication
|
||||
or use the other web-based login features of your OIDC provider
|
||||
during Resource Owner Password Credentials Grant logins. AllowPasswordGrant
|
||||
defaults to false.
|
||||
type: boolean
|
||||
type: object
|
||||
claims:
|
||||
description: Claims provides the names of token claims that will be
|
||||
|
||||
@@ -32,6 +32,22 @@ rules:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [oidcidentityproviders/status]
|
||||
verbs: [get, patch, update]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [ldapidentityproviders]
|
||||
verbs: [get, list, watch]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [ldapidentityproviders/status]
|
||||
verbs: [get, patch, update]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [activedirectoryidentityproviders]
|
||||
verbs: [get, list, watch]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [activedirectoryidentityproviders/status]
|
||||
verbs: [get, patch, update]
|
||||
#! We want to be able to read pods/replicasets/deployment so we can learn who our deployment is to set
|
||||
#! as an owner reference.
|
||||
- apiGroups: [""]
|
||||
@@ -40,6 +56,9 @@ rules:
|
||||
- apiGroups: [apps]
|
||||
resources: [replicasets,deployments]
|
||||
verbs: [get]
|
||||
- apiGroups: [ coordination.k8s.io ]
|
||||
resources: [ leases ]
|
||||
verbs: [ create, get, update ]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
|
||||
#@ load("helpers.lib.yaml", "labels", "deploymentPodLabel", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
|
||||
|
||||
#@ if data.values.service_http_nodeport_port or data.values.service_https_nodeport_port:
|
||||
---
|
||||
@@ -12,10 +12,12 @@ metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("nodeport")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: #@ data.values.app_name
|
||||
selector: #@ deploymentPodLabel()
|
||||
ports:
|
||||
#@ if data.values.service_http_nodeport_port:
|
||||
- name: http
|
||||
@@ -45,9 +47,12 @@ metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("clusterip")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector: #@ defaultLabel()
|
||||
selector: #@ deploymentPodLabel()
|
||||
ports:
|
||||
#@ if data.values.service_http_clusterip_port:
|
||||
- name: http
|
||||
@@ -71,9 +76,12 @@ metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("loadbalancer")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
#! prevent kapp from altering the selector of our services to match kubectl behavior
|
||||
annotations:
|
||||
kapp.k14s.io/disable-default-label-scoping-rules: ""
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector: #@ defaultLabel()
|
||||
selector: #@ deploymentPodLabel()
|
||||
#@ if data.values.service_loadbalancer_ip:
|
||||
loadBalancerIP: #@ data.values.service_loadbalancer_ip
|
||||
#@ end
|
||||
|
||||
@@ -57,11 +57,19 @@ service_loadbalancer_ip: #! e.g. 1.2.3.4
|
||||
#! information), trace (timing information), all (kitchen sink).
|
||||
log_level: #! By default, when this value is left unset, only warnings and errors are printed. There is no way to suppress warning and error logs.
|
||||
|
||||
run_as_user: 1001 #! run_as_user specifies the user ID that will own the local-user-authenticator process
|
||||
run_as_group: 1001 #! run_as_group specifies the group ID that will own the local-user-authenticator process
|
||||
run_as_user: 1001 #! run_as_user specifies the user ID that will own the process
|
||||
run_as_group: 1001 #! run_as_group specifies the group ID that will own the process
|
||||
|
||||
#! Specify the API group suffix for all Pinniped API groups. By default, this is set to
|
||||
#! pinniped.dev, so Pinniped API groups will look like foo.pinniped.dev,
|
||||
#! authentication.concierge.pinniped.dev, etc. As an example, if this is set to tuna.io, then
|
||||
#! Pinniped API groups will look like foo.tuna.io. authentication.concierge.tuna.io, etc.
|
||||
api_group_suffix: pinniped.dev
|
||||
|
||||
#! Set the standard golang HTTPS_PROXY and NO_PROXY environment variables on the Supervisor containers.
|
||||
#! These will be used when the Supervisor makes backend-to-backend calls to upstream identity providers using HTTPS,
|
||||
#! e.g. when the Supervisor fetches discovery documents, JWKS keys, and tokens from an upstream OIDC Provider.
|
||||
#! The Supervisor never makes insecure HTTP calls, so there is no reason to set HTTP_PROXY.
|
||||
#! Optional.
|
||||
https_proxy: #! e.g. http://proxy.example.com
|
||||
no_proxy: "$(KUBERNETES_SERVICE_HOST),169.254.169.254,127.0.0.1,localhost,.svc,.cluster.local" #! do not proxy Kubernetes endpoints
|
||||
|
||||
@@ -22,3 +22,21 @@ metadata:
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("oidcidentityproviders.idp.supervisor")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
|
||||
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"ldapidentityproviders.idp.supervisor.pinniped.dev"}}), expects=1
|
||||
---
|
||||
metadata:
|
||||
#@overlay/match missing_ok=True
|
||||
labels: #@ labels()
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("ldapidentityproviders.idp.supervisor")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
|
||||
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"activedirectoryidentityproviders.idp.supervisor.pinniped.dev"}}), expects=1
|
||||
---
|
||||
metadata:
|
||||
#@overlay/match missing_ok=True
|
||||
labels: #@ labels()
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("activedirectoryidentityproviders.idp.supervisor")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
|
||||
674
generated/1.17/README.adoc
generated
674
generated/1.17/README.adoc
generated
@@ -8,6 +8,8 @@
|
||||
- xref:{anchor_prefix}-authentication-concierge-pinniped-dev-v1alpha1[$$authentication.concierge.pinniped.dev/v1alpha1$$]
|
||||
- xref:{anchor_prefix}-config-concierge-pinniped-dev-v1alpha1[$$config.concierge.pinniped.dev/v1alpha1$$]
|
||||
- xref:{anchor_prefix}-config-supervisor-pinniped-dev-v1alpha1[$$config.supervisor.pinniped.dev/v1alpha1$$]
|
||||
- xref:{anchor_prefix}-identity-concierge-pinniped-dev-identity[$$identity.concierge.pinniped.dev/identity$$]
|
||||
- xref:{anchor_prefix}-identity-concierge-pinniped-dev-v1alpha1[$$identity.concierge.pinniped.dev/v1alpha1$$]
|
||||
- xref:{anchor_prefix}-idp-supervisor-pinniped-dev-v1alpha1[$$idp.supervisor.pinniped.dev/v1alpha1$$]
|
||||
- xref:{anchor_prefix}-login-concierge-pinniped-dev-v1alpha1[$$login.concierge.pinniped.dev/v1alpha1$$]
|
||||
|
||||
@@ -218,7 +220,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge configuration
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuer"]
|
||||
==== CredentialIssuer
|
||||
|
||||
Describes the configuration status of a Pinniped credential issuer.
|
||||
CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -230,7 +232,27 @@ Describes the configuration status of a Pinniped credential issuer.
|
||||
| Field | Description
|
||||
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
|
||||
|
||||
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | Status of the credential issuer.
|
||||
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$]__ | Spec describes the intended configuration of the Concierge.
|
||||
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | CredentialIssuerStatus describes the status of the Concierge.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerfrontend"]
|
||||
==== CredentialIssuerFrontend
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstrategy[$$CredentialIssuerStrategy$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`type`* __FrontendType__ | Type describes which frontend mechanism clients can use with a strategy.
|
||||
| *`tokenCredentialRequestInfo`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo[$$TokenCredentialRequestAPIInfo$$]__ | TokenCredentialRequestAPIInfo describes the parameters for the TokenCredentialRequest API on this Concierge. This field is only set when Type is "TokenCredentialRequestAPI".
|
||||
| *`impersonationProxyInfo`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyinfo[$$ImpersonationProxyInfo$$]__ | ImpersonationProxyInfo describes the parameters for the impersonation proxy on this Concierge. This field is only set when Type is "ImpersonationProxy".
|
||||
|===
|
||||
|
||||
|
||||
@@ -254,10 +276,27 @@ Describes the configuration status of a Pinniped credential issuer.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerspec"]
|
||||
==== CredentialIssuerSpec
|
||||
|
||||
CredentialIssuerSpec describes the intended configuration of the Concierge.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuer[$$CredentialIssuer$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`impersonationProxy`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$]__ | ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus"]
|
||||
==== CredentialIssuerStatus
|
||||
|
||||
Status of a credential issuer.
|
||||
CredentialIssuerStatus describes the status of the Concierge.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
@@ -268,7 +307,7 @@ Status of a credential issuer.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`strategies`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstrategy[$$CredentialIssuerStrategy$$] array__ | List of integration strategies that were attempted by Pinniped.
|
||||
| *`kubeConfigInfo`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerkubeconfiginfo[$$CredentialIssuerKubeConfigInfo$$]__ | Information needed to form a valid Pinniped-based kubeconfig using this credential issuer.
|
||||
| *`kubeConfigInfo`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerkubeconfiginfo[$$CredentialIssuerKubeConfigInfo$$]__ | 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.
|
||||
|===
|
||||
|
||||
|
||||
@@ -290,6 +329,107 @@ Status of a credential issuer.
|
||||
| *`reason`* __StrategyReason__ | Reason for the current status.
|
||||
| *`message`* __string__ | Human-readable description of the current status.
|
||||
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#time-v1-meta[$$Time$$]__ | When the status was last checked.
|
||||
| *`frontend`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerfrontend[$$CredentialIssuerFrontend$$]__ | Frontend describes how clients can connect using this strategy.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyinfo"]
|
||||
==== ImpersonationProxyInfo
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerfrontend[$$CredentialIssuerFrontend$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`endpoint`* __string__ | Endpoint is the HTTPS endpoint of the impersonation proxy.
|
||||
| *`certificateAuthorityData`* __string__ | CertificateAuthorityData is the base64-encoded PEM CA bundle of the impersonation proxy.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxymode"]
|
||||
==== ImpersonationProxyMode (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicespec"]
|
||||
==== ImpersonationProxyServiceSpec
|
||||
|
||||
ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec[$$ImpersonationProxySpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`type`* __ImpersonationProxyServiceType__ | Type specifies the type of Service to provision for the impersonation proxy.
|
||||
If the type is "None", then the "spec.impersonationProxy.externalEndpoint" field must be set to a non-empty value so that the Concierge can properly advertise the endpoint in the CredentialIssuer's status.
|
||||
| *`loadBalancerIP`* __string__ | LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service. This is not supported on all cloud providers.
|
||||
| *`annotations`* __object (keys:string, values:string)__ | Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicetype"]
|
||||
==== ImpersonationProxyServiceType (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyspec"]
|
||||
==== ImpersonationProxySpec
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerspec[$$CredentialIssuerSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`mode`* __ImpersonationProxyMode__ | Mode configures whether the impersonation proxy should be started: - "disabled" explicitly disables the impersonation proxy. This is the default. - "enabled" explicitly enables the impersonation proxy. - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running.
|
||||
| *`service`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-impersonationproxyservicespec[$$ImpersonationProxyServiceSpec$$]__ | Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients.
|
||||
| *`externalEndpoint`* __string__ | ExternalEndpoint describes the HTTPS endpoint where the proxy will be exposed. If not set, the proxy will be served using the external name of the LoadBalancer service or the cluster service DNS name.
|
||||
This field must be non-empty when spec.impersonationProxy.service.type is "None".
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-tokencredentialrequestapiinfo"]
|
||||
==== TokenCredentialRequestAPIInfo
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerfrontend[$$CredentialIssuerFrontend$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`server`* __string__ | Server is the Kubernetes API server URL.
|
||||
| *`certificateAuthorityData`* __string__ | CertificateAuthorityData is the base64-encoded Kubernetes API server CA bundle.
|
||||
|===
|
||||
|
||||
|
||||
@@ -404,6 +544,203 @@ FederationDomainTLSSpec is a struct that describes the TLS configuration for an
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-identity-concierge-pinniped-dev-identity"]
|
||||
=== identity.concierge.pinniped.dev/identity
|
||||
|
||||
Package identity is the internal version of the Pinniped identity API.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-extravalue"]
|
||||
==== ExtraValue
|
||||
|
||||
ExtraValue masks the value so protobuf can generate
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-userinfo[$$UserInfo$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-kubernetesuserinfo"]
|
||||
==== KubernetesUserInfo
|
||||
|
||||
KubernetesUserInfo represents the current authenticated user, exactly as Kubernetes understands it. Copied from the Kubernetes token review API.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-whoamirequeststatus[$$WhoAmIRequestStatus$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`User`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-userinfo[$$UserInfo$$]__ | User is the UserInfo associated with the current user.
|
||||
| *`Audiences`* __string array__ | Audiences are audience identifiers chosen by the authenticator.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-userinfo"]
|
||||
==== UserInfo
|
||||
|
||||
UserInfo holds the information about the user needed to implement the user.Info interface.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-kubernetesuserinfo[$$KubernetesUserInfo$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`Username`* __string__ | The name that uniquely identifies this user among all active users.
|
||||
| *`UID`* __string__ | A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.
|
||||
| *`Groups`* __string array__ | The names of groups this user is a part of.
|
||||
| *`Extra`* __object (keys:string, values:string array)__ | Any additional information provided by the authenticator.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-whoamirequest"]
|
||||
==== WhoAmIRequest
|
||||
|
||||
WhoAmIRequest submits a request to echo back the current authenticated user.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-whoamirequestlist[$$WhoAmIRequestList$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`ObjectMeta`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ |
|
||||
| *`Spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-whoamirequestspec[$$WhoAmIRequestSpec$$]__ |
|
||||
| *`Status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-whoamirequeststatus[$$WhoAmIRequestStatus$$]__ |
|
||||
|===
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-whoamirequeststatus"]
|
||||
==== WhoAmIRequestStatus
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-whoamirequest[$$WhoAmIRequest$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`KubernetesUserInfo`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-kubernetesuserinfo[$$KubernetesUserInfo$$]__ | The current authenticated user, exactly as Kubernetes understands it.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-identity-concierge-pinniped-dev-v1alpha1"]
|
||||
=== identity.concierge.pinniped.dev/v1alpha1
|
||||
|
||||
Package v1alpha1 is the v1alpha1 version of the Pinniped identity API.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-extravalue"]
|
||||
==== ExtraValue
|
||||
|
||||
ExtraValue masks the value so protobuf can generate
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-userinfo[$$UserInfo$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-kubernetesuserinfo"]
|
||||
==== KubernetesUserInfo
|
||||
|
||||
KubernetesUserInfo represents the current authenticated user, exactly as Kubernetes understands it. Copied from the Kubernetes token review API.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-whoamirequeststatus[$$WhoAmIRequestStatus$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`user`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-userinfo[$$UserInfo$$]__ | User is the UserInfo associated with the current user.
|
||||
| *`audiences`* __string array__ | Audiences are audience identifiers chosen by the authenticator.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-userinfo"]
|
||||
==== UserInfo
|
||||
|
||||
UserInfo holds the information about the user needed to implement the user.Info interface.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-kubernetesuserinfo[$$KubernetesUserInfo$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`username`* __string__ | The name that uniquely identifies this user among all active users.
|
||||
| *`uid`* __string__ | A unique value that identifies this user across time. If this user is deleted and another user by the same name is added, they will have different UIDs.
|
||||
| *`groups`* __string array__ | The names of groups this user is a part of.
|
||||
| *`extra`* __object (keys:string, values:string array)__ | Any additional information provided by the authenticator.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-whoamirequest"]
|
||||
==== WhoAmIRequest
|
||||
|
||||
WhoAmIRequest submits a request to echo back the current authenticated user.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-whoamirequestlist[$$WhoAmIRequestList$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
|
||||
|
||||
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-whoamirequestspec[$$WhoAmIRequestSpec$$]__ |
|
||||
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-whoamirequeststatus[$$WhoAmIRequestStatus$$]__ |
|
||||
|===
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-whoamirequeststatus"]
|
||||
==== WhoAmIRequestStatus
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-whoamirequest[$$WhoAmIRequest$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`kubernetesUserInfo`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-identity-v1alpha1-kubernetesuserinfo[$$KubernetesUserInfo$$]__ | The current authenticated user, exactly as Kubernetes understands it.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-idp-supervisor-pinniped-dev-v1alpha1"]
|
||||
=== idp.supervisor.pinniped.dev/v1alpha1
|
||||
|
||||
@@ -411,13 +748,166 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity pro
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition"]
|
||||
==== Condition
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovider"]
|
||||
==== ActiveDirectoryIdentityProvider
|
||||
|
||||
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.
|
||||
ActiveDirectoryIdentityProvider describes the configuration of an upstream Microsoft Active Directory identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderlist[$$ActiveDirectoryIdentityProviderList$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
|
||||
|
||||
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]__ | Spec for configuring the identity provider.
|
||||
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderstatus[$$ActiveDirectoryIdentityProviderStatus$$]__ | Status of the identity provider.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderbind"]
|
||||
==== ActiveDirectoryIdentityProviderBind
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an Active Directory bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearch"]
|
||||
==== ActiveDirectoryIdentityProviderGroupSearch
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". Optional, when not specified it will be based on the result of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse). The default behavior searches your entire domain for groups. It may make sense to specify a subtree as a search base if you wish to exclude some groups for security reasons or to make searches faster.
|
||||
| *`filter`* __string__ | Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})". This searches nested groups by default. Note that nested group search can be slow for some Active Directory servers. To disable it, you can set the filter to "(&(objectClass=group)(member={})"
|
||||
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearchattributes[$$ActiveDirectoryIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as the result of the group search.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearchattributes"]
|
||||
==== ActiveDirectoryIdentityProviderGroupSearchAttributes
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearch[$$ActiveDirectoryIdentityProviderGroupSearch$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`groupName`* __string__ | GroupName specifies the name of the attribute in the Active Directory entries whose value shall become a group name in the user's list of groups after a successful authentication. The value of this field is case-sensitive and must match the case of the attribute name returned by the ActiveDirectory server in the user's entry. E.g. "cn" for common name. Distinguished names can be used by specifying lower-case "dn". Optional. When not specified, this defaults to a custom field that looks like "sAMAccountName@domain", where domain is constructed from the domain components of the group DN.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec"]
|
||||
==== ActiveDirectoryIdentityProviderSpec
|
||||
|
||||
Spec for configuring an ActiveDirectory identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovider[$$ActiveDirectoryIdentityProvider$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`host`* __string__ | Host is the hostname of this Active Directory identity provider, i.e., where to connect. For example: ldap.example.com:636.
|
||||
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
|
||||
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderbind[$$ActiveDirectoryIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the ActiveDirectory server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
|
||||
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearch[$$ActiveDirectoryIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in Active Directory.
|
||||
| *`groupSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovidergroupsearch[$$ActiveDirectoryIdentityProviderGroupSearch$$]__ | GroupSearch contains the configuration for searching for a user's group membership in ActiveDirectory.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderstatus"]
|
||||
==== ActiveDirectoryIdentityProviderStatus
|
||||
|
||||
Status of an Active Directory identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityprovider[$$ActiveDirectoryIdentityProvider$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`phase`* __ActiveDirectoryIdentityProviderPhase__ | Phase summarizes the overall status of the ActiveDirectoryIdentityProvider.
|
||||
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearch"]
|
||||
==== ActiveDirectoryIdentityProviderUserSearch
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com". Optional, when not specified it will be based on the result of a query for the defaultNamingContext (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse). The default behavior searches your entire domain for users. It may make sense to specify a subtree as a search base if you wish to exclude some users or to make searches faster.
|
||||
| *`filter`* __string__ | Filter is the search filter which should be applied when searching for users. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will be '(&(objectClass=person)(!(objectClass=computer))(!(showInAdvancedViewOnly=TRUE))(|(sAMAccountName={}")(mail={})(userPrincipalName={})(sAMAccountType=805306368))' This means that the user is a person, is not a computer, the sAMAccountType is for a normal user account, and is not shown in advanced view only (which would likely mean its a system created service account with advanced permissions). Also, either the sAMAccountName, the userPrincipalName, or the mail attribute matches the input username.
|
||||
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearchattributes[$$ActiveDirectoryIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the ActiveDirectory entry which was found as the result of the user search.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearchattributes"]
|
||||
==== ActiveDirectoryIdentityProviderUserSearchAttributes
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderusersearch[$$ActiveDirectoryIdentityProviderUserSearch$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`username`* __string__ | Username specifies the name of the attribute in Active Directory entry whose value shall become the username of the user after a successful authentication. Optional, when empty this defaults to "userPrincipalName".
|
||||
| *`uid`* __string__ | UID specifies the name of the attribute in the ActiveDirectory entry which whose value shall be used to uniquely identify the user within this ActiveDirectory provider after a successful authentication. Optional, when empty this defaults to "objectGUID".
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition"]
|
||||
==== Condition
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderstatus[$$ActiveDirectoryIdentityProviderStatus$$]
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus[$$OIDCIdentityProviderStatus$$]
|
||||
****
|
||||
|
||||
@@ -433,6 +923,169 @@ Condition status of a resource (mirrored from the metav1.Condition type added in
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-conditionstatus"]
|
||||
==== ConditionStatus (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovider"]
|
||||
==== LDAPIdentityProvider
|
||||
|
||||
LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access Protocol (LDAP) identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderlist[$$LDAPIdentityProviderList$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
|
||||
|
||||
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]__ | Spec for configuring the identity provider.
|
||||
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus[$$LDAPIdentityProviderStatus$$]__ | Status of the identity provider.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind"]
|
||||
==== LDAPIdentityProviderBind
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the username and password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com". The password must be non-empty.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch"]
|
||||
==== LDAPIdentityProviderGroupSearch
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". When not specified, no group search will be performed and authenticated users will not belong to any groups from the LDAP provider. Also, when not specified, the values of Filter and Attributes are ignored.
|
||||
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for groups for a user. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as "member={}".
|
||||
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes[$$LDAPIdentityProviderGroupSearchAttributes$$]__ | Attributes specifies how the group's information should be read from each LDAP entry which was found as the result of the group search.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearchattributes"]
|
||||
==== LDAPIdentityProviderGroupSearchAttributes
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`groupName`* __string__ | GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name in the user's list of groups after a successful authentication. The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. E.g. "cn" for common name. Distinguished names can be used by specifying lower-case "dn". Optional. When not specified, the default will act as if the GroupName were specified as "dn" (distinguished name).
|
||||
|===
|
||||
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec"]
|
||||
==== LDAPIdentityProviderSpec
|
||||
|
||||
Spec for configuring an LDAP identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`host`* __string__ | Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
|
||||
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS contains the connection settings for how to establish the connection to the Host.
|
||||
| *`bind`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderbind[$$LDAPIdentityProviderBind$$]__ | Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
|
||||
| *`userSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]__ | UserSearch contains the configuration for searching for a user by name in the LDAP provider.
|
||||
| *`groupSearch`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovidergroupsearch[$$LDAPIdentityProviderGroupSearch$$]__ | GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderstatus"]
|
||||
==== LDAPIdentityProviderStatus
|
||||
|
||||
Status of an LDAP identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityprovider[$$LDAPIdentityProvider$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`phase`* __LDAPIdentityProviderPhase__ | Phase summarizes the overall status of the LDAPIdentityProvider.
|
||||
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch"]
|
||||
==== LDAPIdentityProviderUserSearch
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`base`* __string__ | Base is the dn (distinguished name) that should be used as the search base when searching for users. E.g. "ou=users,dc=example,dc=com".
|
||||
| *`filter`* __string__ | Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the username for which the search is being run. E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see https://ldap.com/ldap-filters. Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. Optional. When not specified, the default will act as if the Filter were specified as the value from Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be explicitly specified, since the default value of "dn={}" would not work.
|
||||
| *`attributes`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes[$$LDAPIdentityProviderUserSearchAttributes$$]__ | Attributes specifies how the user's information should be read from the LDAP entry which was found as the result of the user search.
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearchattributes"]
|
||||
==== LDAPIdentityProviderUserSearchAttributes
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderusersearch[$$LDAPIdentityProviderUserSearch$$]
|
||||
****
|
||||
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`username`* __string__ | Username specifies the name of the attribute in the LDAP entry whose value shall become the username of the user after a successful authentication. This would typically be the same attribute name used in the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default value of "dn={}" would not work.
|
||||
| *`uid`* __string__ | UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID". The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcauthorizationconfig"]
|
||||
==== OIDCAuthorizationConfig
|
||||
|
||||
@@ -446,7 +1099,8 @@ OIDCAuthorizationConfig provides information about how to form the OAuth2 author
|
||||
[cols="25a,75a", options="header"]
|
||||
|===
|
||||
| Field | Description
|
||||
| *`additionalScopes`* __string array__ | AdditionalScopes are the scopes in addition to "openid" that will be requested as part of the authorization request flow with an OIDC identity provider. By default only the "openid" scope will be requested.
|
||||
| *`additionalScopes`* __string array__ | AdditionalScopes are the scopes in addition to "openid" that will be requested as part of the authorization request flow with an OIDC identity provider. In the case of a Resource Owner Password Credentials Grant flow, AdditionalScopes are the scopes in addition to "openid" that will be requested as part of the token request (see also the allowPasswordGrant field). By default, only the "openid" scope will be requested.
|
||||
| *`allowPasswordGrant`* __boolean__ | AllowPasswordGrant, when true, will allow the use of OAuth 2.0's Resource Owner Password Credentials Grant (see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3) to authenticate to the OIDC provider using a username and password without a web browser, in addition to the usual browser-based OIDC Authorization Code Flow. The Resource Owner Password Credentials Grant is not officially part of the OIDC specification, so it may not be supported by your OIDC provider. If your OIDC provider supports returning ID tokens from a Resource Owner Password Credentials Grant token request, then you can choose to set this field to true. This will allow end users to choose to present their username and password to the kubectl CLI (using the Pinniped plugin) to authenticate to the cluster, without using a web browser to log in as is customary in OIDC Authorization Code Flow. This may be convenient for users, especially for identities from your OIDC provider which are not intended to represent a human actor, such as service accounts performing actions in a CI/CD environment. Even if your OIDC provider supports it, you may wish to disable this behavior by setting this field to false when you prefer to only allow users of this OIDCIdentityProvider to log in via the browser-based OIDC Authorization Code Flow. Using the Resource Owner Password Credentials Grant means that the Pinniped CLI and Pinniped Supervisor will directly handle your end users' passwords (similar to LDAPIdentityProvider), and you will not be able to require multi-factor authentication or use the other web-based login features of your OIDC provider during Resource Owner Password Credentials Grant logins. AllowPasswordGrant defaults to false.
|
||||
|===
|
||||
|
||||
|
||||
@@ -542,7 +1196,7 @@ Status of an OIDC identity provider.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`phase`* __OIDCIdentityProviderPhase__ | Phase summarizes the overall status of the OIDCIdentityProvider.
|
||||
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]__ | Represents the observations of an identity provider's current state.
|
||||
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of an identity provider's current state.
|
||||
|===
|
||||
|
||||
|
||||
@@ -553,6 +1207,8 @@ Status of an OIDC identity provider.
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-activedirectoryidentityproviderspec[$$ActiveDirectoryIdentityProviderSpec$$]
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-ldapidentityproviderspec[$$LDAPIdentityProviderSpec$$]
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
|
||||
****
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
@@ -1,40 +1,144 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +kubebuilder:validation:Enum=KubeClusterSigningCertificate
|
||||
// StrategyType enumerates a type of "strategy" used to implement credential access on a cluster.
|
||||
// +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy
|
||||
type StrategyType string
|
||||
|
||||
// FrontendType enumerates a type of "frontend" used to provide access to users of a cluster.
|
||||
// +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy
|
||||
type FrontendType string
|
||||
|
||||
// StrategyStatus enumerates whether a strategy is working on a cluster.
|
||||
// +kubebuilder:validation:Enum=Success;Error
|
||||
type StrategyStatus string
|
||||
|
||||
// +kubebuilder:validation:Enum=FetchedKey;CouldNotFetchKey
|
||||
// StrategyReason enumerates the detailed reason why a strategy is in a particular status.
|
||||
// +kubebuilder:validation:Enum=Listening;Pending;Disabled;ErrorDuringSetup;CouldNotFetchKey;CouldNotGetClusterInfo;FetchedKey
|
||||
type StrategyReason string
|
||||
|
||||
const (
|
||||
KubeClusterSigningCertificateStrategyType = StrategyType("KubeClusterSigningCertificate")
|
||||
ImpersonationProxyStrategyType = StrategyType("ImpersonationProxy")
|
||||
|
||||
TokenCredentialRequestAPIFrontendType = FrontendType("TokenCredentialRequestAPI")
|
||||
ImpersonationProxyFrontendType = FrontendType("ImpersonationProxy")
|
||||
|
||||
SuccessStrategyStatus = StrategyStatus("Success")
|
||||
ErrorStrategyStatus = StrategyStatus("Error")
|
||||
|
||||
CouldNotFetchKeyStrategyReason = StrategyReason("CouldNotFetchKey")
|
||||
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
|
||||
ListeningStrategyReason = StrategyReason("Listening")
|
||||
PendingStrategyReason = StrategyReason("Pending")
|
||||
DisabledStrategyReason = StrategyReason("Disabled")
|
||||
ErrorDuringSetupStrategyReason = StrategyReason("ErrorDuringSetup")
|
||||
CouldNotFetchKeyStrategyReason = StrategyReason("CouldNotFetchKey")
|
||||
CouldNotGetClusterInfoStrategyReason = StrategyReason("CouldNotGetClusterInfo")
|
||||
FetchedKeyStrategyReason = StrategyReason("FetchedKey")
|
||||
)
|
||||
|
||||
// Status of a credential issuer.
|
||||
// CredentialIssuerSpec describes the intended configuration of the Concierge.
|
||||
type CredentialIssuerSpec struct {
|
||||
// ImpersonationProxy describes the intended configuration of the Concierge impersonation proxy.
|
||||
ImpersonationProxy *ImpersonationProxySpec `json:"impersonationProxy"`
|
||||
}
|
||||
|
||||
// ImpersonationProxyMode enumerates the configuration modes for the impersonation proxy.
|
||||
//
|
||||
// +kubebuilder:validation:Enum=auto;enabled;disabled
|
||||
type ImpersonationProxyMode string
|
||||
|
||||
const (
|
||||
// ImpersonationProxyModeDisabled explicitly disables the impersonation proxy.
|
||||
ImpersonationProxyModeDisabled = ImpersonationProxyMode("disabled")
|
||||
|
||||
// ImpersonationProxyModeEnabled explicitly enables the impersonation proxy.
|
||||
ImpersonationProxyModeEnabled = ImpersonationProxyMode("enabled")
|
||||
|
||||
// ImpersonationProxyModeAuto enables or disables the impersonation proxy based upon the cluster in which it is running.
|
||||
ImpersonationProxyModeAuto = ImpersonationProxyMode("auto")
|
||||
)
|
||||
|
||||
// ImpersonationProxyServiceType enumerates the types of service that can be provisioned for the impersonation proxy.
|
||||
//
|
||||
// +kubebuilder:validation:Enum=LoadBalancer;ClusterIP;None
|
||||
type ImpersonationProxyServiceType string
|
||||
|
||||
const (
|
||||
// ImpersonationProxyServiceTypeLoadBalancer provisions a service of type LoadBalancer.
|
||||
ImpersonationProxyServiceTypeLoadBalancer = ImpersonationProxyServiceType("LoadBalancer")
|
||||
|
||||
// ImpersonationProxyServiceTypeClusterIP provisions a service of type ClusterIP.
|
||||
ImpersonationProxyServiceTypeClusterIP = ImpersonationProxyServiceType("ClusterIP")
|
||||
|
||||
// ImpersonationProxyServiceTypeNone does not automatically provision any service.
|
||||
ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None")
|
||||
)
|
||||
|
||||
// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy.
|
||||
type ImpersonationProxySpec struct {
|
||||
// Mode configures whether the impersonation proxy should be started:
|
||||
// - "disabled" explicitly disables the impersonation proxy. This is the default.
|
||||
// - "enabled" explicitly enables the impersonation proxy.
|
||||
// - "auto" enables or disables the impersonation proxy based upon the cluster in which it is running.
|
||||
Mode ImpersonationProxyMode `json:"mode"`
|
||||
|
||||
// Service describes the configuration of the Service provisioned to expose the impersonation proxy to clients.
|
||||
//
|
||||
// +kubebuilder:default:={"type": "LoadBalancer"}
|
||||
Service ImpersonationProxyServiceSpec `json:"service"`
|
||||
|
||||
// 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".
|
||||
//
|
||||
// +optional
|
||||
ExternalEndpoint string `json:"externalEndpoint,omitempty"`
|
||||
}
|
||||
|
||||
// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy.
|
||||
type ImpersonationProxyServiceSpec struct {
|
||||
// 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.
|
||||
//
|
||||
// +kubebuilder:default:="LoadBalancer"
|
||||
Type ImpersonationProxyServiceType `json:"type,omitempty"`
|
||||
|
||||
// LoadBalancerIP specifies the IP address to set in the spec.loadBalancerIP field of the provisioned Service.
|
||||
// This is not supported on all cloud providers.
|
||||
//
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:MaxLength=255
|
||||
// +optional
|
||||
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
|
||||
|
||||
// Annotations specifies zero or more key/value pairs to set as annotations on the provisioned Service.
|
||||
//
|
||||
// +optional
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// CredentialIssuerStatus describes the status of the Concierge.
|
||||
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"`
|
||||
}
|
||||
|
||||
// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer.
|
||||
// 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
|
||||
@@ -46,7 +150,7 @@ type CredentialIssuerKubeConfigInfo struct {
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData"`
|
||||
}
|
||||
|
||||
// Status of an integration strategy that was attempted by Pinniped.
|
||||
// CredentialIssuerStrategy describes the status of an integration strategy that was attempted by Pinniped.
|
||||
type CredentialIssuerStrategy struct {
|
||||
// Type of integration attempted.
|
||||
Type StrategyType `json:"type"`
|
||||
@@ -63,9 +167,50 @@ type CredentialIssuerStrategy struct {
|
||||
|
||||
// When the status was last checked.
|
||||
LastUpdateTime metav1.Time `json:"lastUpdateTime"`
|
||||
|
||||
// Frontend describes how clients can connect using this strategy.
|
||||
Frontend *CredentialIssuerFrontend `json:"frontend,omitempty"`
|
||||
}
|
||||
|
||||
// Describes the configuration status of a Pinniped credential issuer.
|
||||
// CredentialIssuerFrontend describes how to connect using a particular integration strategy.
|
||||
type CredentialIssuerFrontend struct {
|
||||
// Type describes which frontend mechanism clients can use with a strategy.
|
||||
Type FrontendType `json:"type"`
|
||||
|
||||
// TokenCredentialRequestAPIInfo describes the parameters for the TokenCredentialRequest API on this Concierge.
|
||||
// This field is only set when Type is "TokenCredentialRequestAPI".
|
||||
TokenCredentialRequestAPIInfo *TokenCredentialRequestAPIInfo `json:"tokenCredentialRequestInfo,omitempty"`
|
||||
|
||||
// ImpersonationProxyInfo describes the parameters for the impersonation proxy on this Concierge.
|
||||
// This field is only set when Type is "ImpersonationProxy".
|
||||
ImpersonationProxyInfo *ImpersonationProxyInfo `json:"impersonationProxyInfo,omitempty"`
|
||||
}
|
||||
|
||||
// TokenCredentialRequestAPIInfo describes the parameters for the TokenCredentialRequest API on this Concierge.
|
||||
type TokenCredentialRequestAPIInfo struct {
|
||||
// Server is the Kubernetes API server URL.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:Pattern=`^https://|^http://`
|
||||
Server string `json:"server"`
|
||||
|
||||
// CertificateAuthorityData is the base64-encoded Kubernetes API server CA bundle.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData"`
|
||||
}
|
||||
|
||||
// ImpersonationProxyInfo describes the parameters for the impersonation proxy on this Concierge.
|
||||
type ImpersonationProxyInfo struct {
|
||||
// Endpoint is the HTTPS endpoint of the impersonation proxy.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:Pattern=`^https://`
|
||||
Endpoint string `json:"endpoint"`
|
||||
|
||||
// CertificateAuthorityData is the base64-encoded PEM CA bundle of the impersonation proxy.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
CertificateAuthorityData string `json:"certificateAuthorityData"`
|
||||
}
|
||||
|
||||
// CredentialIssuer describes the configuration and status of the Pinniped Concierge credential issuer.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
@@ -75,12 +220,18 @@ type CredentialIssuer struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Status of the credential issuer.
|
||||
// Spec describes the intended configuration of the Concierge.
|
||||
//
|
||||
// +optional
|
||||
Spec CredentialIssuerSpec `json:"spec"`
|
||||
|
||||
// CredentialIssuerStatus describes the status of the Concierge.
|
||||
//
|
||||
// +optional
|
||||
Status CredentialIssuerStatus `json:"status"`
|
||||
}
|
||||
|
||||
// List of CredentialIssuer objects.
|
||||
// CredentialIssuerList is a list of CredentialIssuer objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type CredentialIssuerList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
@@ -16,6 +16,7 @@ func (in *CredentialIssuer) DeepCopyInto(out *CredentialIssuer) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
@@ -38,6 +39,32 @@ func (in *CredentialIssuer) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CredentialIssuerFrontend) DeepCopyInto(out *CredentialIssuerFrontend) {
|
||||
*out = *in
|
||||
if in.TokenCredentialRequestAPIInfo != nil {
|
||||
in, out := &in.TokenCredentialRequestAPIInfo, &out.TokenCredentialRequestAPIInfo
|
||||
*out = new(TokenCredentialRequestAPIInfo)
|
||||
**out = **in
|
||||
}
|
||||
if in.ImpersonationProxyInfo != nil {
|
||||
in, out := &in.ImpersonationProxyInfo, &out.ImpersonationProxyInfo
|
||||
*out = new(ImpersonationProxyInfo)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialIssuerFrontend.
|
||||
func (in *CredentialIssuerFrontend) DeepCopy() *CredentialIssuerFrontend {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CredentialIssuerFrontend)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CredentialIssuerKubeConfigInfo) DeepCopyInto(out *CredentialIssuerKubeConfigInfo) {
|
||||
*out = *in
|
||||
@@ -87,6 +114,27 @@ func (in *CredentialIssuerList) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CredentialIssuerSpec) DeepCopyInto(out *CredentialIssuerSpec) {
|
||||
*out = *in
|
||||
if in.ImpersonationProxy != nil {
|
||||
in, out := &in.ImpersonationProxy, &out.ImpersonationProxy
|
||||
*out = new(ImpersonationProxySpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialIssuerSpec.
|
||||
func (in *CredentialIssuerSpec) DeepCopy() *CredentialIssuerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CredentialIssuerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CredentialIssuerStatus) DeepCopyInto(out *CredentialIssuerStatus) {
|
||||
*out = *in
|
||||
@@ -119,6 +167,11 @@ func (in *CredentialIssuerStatus) DeepCopy() *CredentialIssuerStatus {
|
||||
func (in *CredentialIssuerStrategy) DeepCopyInto(out *CredentialIssuerStrategy) {
|
||||
*out = *in
|
||||
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
|
||||
if in.Frontend != nil {
|
||||
in, out := &in.Frontend, &out.Frontend
|
||||
*out = new(CredentialIssuerFrontend)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,3 +184,75 @@ func (in *CredentialIssuerStrategy) DeepCopy() *CredentialIssuerStrategy {
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImpersonationProxyInfo) DeepCopyInto(out *ImpersonationProxyInfo) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxyInfo.
|
||||
func (in *ImpersonationProxyInfo) DeepCopy() *ImpersonationProxyInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImpersonationProxyInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImpersonationProxyServiceSpec) DeepCopyInto(out *ImpersonationProxyServiceSpec) {
|
||||
*out = *in
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxyServiceSpec.
|
||||
func (in *ImpersonationProxyServiceSpec) DeepCopy() *ImpersonationProxyServiceSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImpersonationProxyServiceSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImpersonationProxySpec) DeepCopyInto(out *ImpersonationProxySpec) {
|
||||
*out = *in
|
||||
in.Service.DeepCopyInto(&out.Service)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxySpec.
|
||||
func (in *ImpersonationProxySpec) DeepCopy() *ImpersonationProxySpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImpersonationProxySpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TokenCredentialRequestAPIInfo) DeepCopyInto(out *TokenCredentialRequestAPIInfo) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TokenCredentialRequestAPIInfo.
|
||||
func (in *TokenCredentialRequestAPIInfo) DeepCopy() *TokenCredentialRequestAPIInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TokenCredentialRequestAPIInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
8
generated/1.17/apis/concierge/identity/doc.go
generated
Normal file
8
generated/1.17/apis/concierge/identity/doc.go
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +groupName=identity.concierge.pinniped.dev
|
||||
|
||||
// Package identity is the internal version of the Pinniped identity API.
|
||||
package identity
|
||||
38
generated/1.17/apis/concierge/identity/register.go
generated
Normal file
38
generated/1.17/apis/concierge/identity/register.go
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const GroupName = "identity.concierge.pinniped.dev"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects.
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns back a Group qualified GroupKind.
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns back a Group qualified GroupResource.
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&WhoAmIRequest{},
|
||||
&WhoAmIRequestList{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
37
generated/1.17/apis/concierge/identity/types_userinfo.go
generated
Normal file
37
generated/1.17/apis/concierge/identity/types_userinfo.go
generated
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
import "fmt"
|
||||
|
||||
// KubernetesUserInfo represents the current authenticated user, exactly as Kubernetes understands it.
|
||||
// Copied from the Kubernetes token review API.
|
||||
type KubernetesUserInfo struct {
|
||||
// User is the UserInfo associated with the current user.
|
||||
User UserInfo
|
||||
// Audiences are audience identifiers chosen by the authenticator.
|
||||
Audiences []string
|
||||
}
|
||||
|
||||
// UserInfo holds the information about the user needed to implement the
|
||||
// user.Info interface.
|
||||
type UserInfo struct {
|
||||
// The name that uniquely identifies this user among all active users.
|
||||
Username string
|
||||
// A unique value that identifies this user across time. If this user is
|
||||
// deleted and another user by the same name is added, they will have
|
||||
// different UIDs.
|
||||
UID string
|
||||
// The names of groups this user is a part of.
|
||||
Groups []string
|
||||
// Any additional information provided by the authenticator.
|
||||
Extra map[string]ExtraValue
|
||||
}
|
||||
|
||||
// ExtraValue masks the value so protobuf can generate
|
||||
type ExtraValue []string
|
||||
|
||||
func (t ExtraValue) String() string {
|
||||
return fmt.Sprintf("%v", []string(t))
|
||||
}
|
||||
40
generated/1.17/apis/concierge/identity/types_whoami.go
generated
Normal file
40
generated/1.17/apis/concierge/identity/types_whoami.go
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identity
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// WhoAmIRequest submits a request to echo back the current authenticated user.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type WhoAmIRequest struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ObjectMeta
|
||||
|
||||
Spec WhoAmIRequestSpec
|
||||
Status WhoAmIRequestStatus
|
||||
}
|
||||
|
||||
type WhoAmIRequestSpec struct {
|
||||
// empty for now but we may add some config here in the future
|
||||
// any such config must be safe in the context of an unauthenticated user
|
||||
}
|
||||
|
||||
type WhoAmIRequestStatus struct {
|
||||
// The current authenticated user, exactly as Kubernetes understands it.
|
||||
KubernetesUserInfo KubernetesUserInfo
|
||||
|
||||
// We may add concierge specific information here in the future.
|
||||
}
|
||||
|
||||
// WhoAmIRequestList is a list of WhoAmIRequest objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type WhoAmIRequestList struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
|
||||
// Items is a list of WhoAmIRequest
|
||||
Items []WhoAmIRequest
|
||||
}
|
||||
4
generated/1.17/apis/concierge/identity/v1alpha1/conversion.go
generated
Normal file
4
generated/1.17/apis/concierge/identity/v1alpha1/conversion.go
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
12
generated/1.17/apis/concierge/identity/v1alpha1/defaults.go
generated
Normal file
12
generated/1.17/apis/concierge/identity/v1alpha1/defaults.go
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
}
|
||||
11
generated/1.17/apis/concierge/identity/v1alpha1/doc.go
generated
Normal file
11
generated/1.17/apis/concierge/identity/v1alpha1/doc.go
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2021 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/1.17/apis/concierge/identity
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=identity.concierge.pinniped.dev
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the Pinniped identity API.
|
||||
package v1alpha1
|
||||
43
generated/1.17/apis/concierge/identity/v1alpha1/register.go
generated
Normal file
43
generated/1.17/apis/concierge/identity/v1alpha1/register.go
generated
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const GroupName = "identity.concierge.pinniped.dev"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects.
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
||||
|
||||
var (
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
|
||||
}
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&WhoAmIRequest{},
|
||||
&WhoAmIRequestList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource.
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
41
generated/1.17/apis/concierge/identity/v1alpha1/types_userinfo.go
generated
Normal file
41
generated/1.17/apis/concierge/identity/v1alpha1/types_userinfo.go
generated
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import "fmt"
|
||||
|
||||
// KubernetesUserInfo represents the current authenticated user, exactly as Kubernetes understands it.
|
||||
// Copied from the Kubernetes token review API.
|
||||
type KubernetesUserInfo struct {
|
||||
// User is the UserInfo associated with the current user.
|
||||
User UserInfo `json:"user"`
|
||||
// Audiences are audience identifiers chosen by the authenticator.
|
||||
// +optional
|
||||
Audiences []string `json:"audiences,omitempty"`
|
||||
}
|
||||
|
||||
// UserInfo holds the information about the user needed to implement the
|
||||
// user.Info interface.
|
||||
type UserInfo struct {
|
||||
// The name that uniquely identifies this user among all active users.
|
||||
Username string `json:"username"`
|
||||
// A unique value that identifies this user across time. If this user is
|
||||
// deleted and another user by the same name is added, they will have
|
||||
// different UIDs.
|
||||
// +optional
|
||||
UID string `json:"uid,omitempty"`
|
||||
// The names of groups this user is a part of.
|
||||
// +optional
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
// Any additional information provided by the authenticator.
|
||||
// +optional
|
||||
Extra map[string]ExtraValue `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
// ExtraValue masks the value so protobuf can generate
|
||||
type ExtraValue []string
|
||||
|
||||
func (t ExtraValue) String() string {
|
||||
return fmt.Sprintf("%v", []string(t))
|
||||
}
|
||||
43
generated/1.17/apis/concierge/identity/v1alpha1/types_whoami.go
generated
Normal file
43
generated/1.17/apis/concierge/identity/v1alpha1/types_whoami.go
generated
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// WhoAmIRequest submits a request to echo back the current authenticated user.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +genclient:onlyVerbs=create
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type WhoAmIRequest struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec WhoAmIRequestSpec `json:"spec,omitempty"`
|
||||
Status WhoAmIRequestStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type WhoAmIRequestSpec struct {
|
||||
// empty for now but we may add some config here in the future
|
||||
// any such config must be safe in the context of an unauthenticated user
|
||||
}
|
||||
|
||||
type WhoAmIRequestStatus struct {
|
||||
// The current authenticated user, exactly as Kubernetes understands it.
|
||||
KubernetesUserInfo KubernetesUserInfo `json:"kubernetesUserInfo"`
|
||||
|
||||
// We may add concierge specific information here in the future.
|
||||
}
|
||||
|
||||
// WhoAmIRequestList is a list of WhoAmIRequest objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type WhoAmIRequestList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Items is a list of WhoAmIRequest
|
||||
Items []WhoAmIRequest `json:"items"`
|
||||
}
|
||||
234
generated/1.17/apis/concierge/identity/v1alpha1/zz_generated.conversion.go
generated
Normal file
234
generated/1.17/apis/concierge/identity/v1alpha1/zz_generated.conversion.go
generated
Normal file
@@ -0,0 +1,234 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by conversion-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
identity "go.pinniped.dev/generated/1.17/apis/concierge/identity"
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(RegisterConversions)
|
||||
}
|
||||
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(s *runtime.Scheme) error {
|
||||
if err := s.AddGeneratedConversionFunc((*KubernetesUserInfo)(nil), (*identity.KubernetesUserInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_KubernetesUserInfo_To_identity_KubernetesUserInfo(a.(*KubernetesUserInfo), b.(*identity.KubernetesUserInfo), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*identity.KubernetesUserInfo)(nil), (*KubernetesUserInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_identity_KubernetesUserInfo_To_v1alpha1_KubernetesUserInfo(a.(*identity.KubernetesUserInfo), b.(*KubernetesUserInfo), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*UserInfo)(nil), (*identity.UserInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_UserInfo_To_identity_UserInfo(a.(*UserInfo), b.(*identity.UserInfo), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*identity.UserInfo)(nil), (*UserInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_identity_UserInfo_To_v1alpha1_UserInfo(a.(*identity.UserInfo), b.(*UserInfo), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*WhoAmIRequest)(nil), (*identity.WhoAmIRequest)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_WhoAmIRequest_To_identity_WhoAmIRequest(a.(*WhoAmIRequest), b.(*identity.WhoAmIRequest), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*identity.WhoAmIRequest)(nil), (*WhoAmIRequest)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_identity_WhoAmIRequest_To_v1alpha1_WhoAmIRequest(a.(*identity.WhoAmIRequest), b.(*WhoAmIRequest), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*WhoAmIRequestList)(nil), (*identity.WhoAmIRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_WhoAmIRequestList_To_identity_WhoAmIRequestList(a.(*WhoAmIRequestList), b.(*identity.WhoAmIRequestList), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*identity.WhoAmIRequestList)(nil), (*WhoAmIRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_identity_WhoAmIRequestList_To_v1alpha1_WhoAmIRequestList(a.(*identity.WhoAmIRequestList), b.(*WhoAmIRequestList), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*WhoAmIRequestSpec)(nil), (*identity.WhoAmIRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_WhoAmIRequestSpec_To_identity_WhoAmIRequestSpec(a.(*WhoAmIRequestSpec), b.(*identity.WhoAmIRequestSpec), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*identity.WhoAmIRequestSpec)(nil), (*WhoAmIRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_identity_WhoAmIRequestSpec_To_v1alpha1_WhoAmIRequestSpec(a.(*identity.WhoAmIRequestSpec), b.(*WhoAmIRequestSpec), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*WhoAmIRequestStatus)(nil), (*identity.WhoAmIRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_WhoAmIRequestStatus_To_identity_WhoAmIRequestStatus(a.(*WhoAmIRequestStatus), b.(*identity.WhoAmIRequestStatus), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*identity.WhoAmIRequestStatus)(nil), (*WhoAmIRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_identity_WhoAmIRequestStatus_To_v1alpha1_WhoAmIRequestStatus(a.(*identity.WhoAmIRequestStatus), b.(*WhoAmIRequestStatus), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_KubernetesUserInfo_To_identity_KubernetesUserInfo(in *KubernetesUserInfo, out *identity.KubernetesUserInfo, s conversion.Scope) error {
|
||||
if err := Convert_v1alpha1_UserInfo_To_identity_UserInfo(&in.User, &out.User, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_KubernetesUserInfo_To_identity_KubernetesUserInfo is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_KubernetesUserInfo_To_identity_KubernetesUserInfo(in *KubernetesUserInfo, out *identity.KubernetesUserInfo, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_KubernetesUserInfo_To_identity_KubernetesUserInfo(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_identity_KubernetesUserInfo_To_v1alpha1_KubernetesUserInfo(in *identity.KubernetesUserInfo, out *KubernetesUserInfo, s conversion.Scope) error {
|
||||
if err := Convert_identity_UserInfo_To_v1alpha1_UserInfo(&in.User, &out.User, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Audiences = *(*[]string)(unsafe.Pointer(&in.Audiences))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_identity_KubernetesUserInfo_To_v1alpha1_KubernetesUserInfo is an autogenerated conversion function.
|
||||
func Convert_identity_KubernetesUserInfo_To_v1alpha1_KubernetesUserInfo(in *identity.KubernetesUserInfo, out *KubernetesUserInfo, s conversion.Scope) error {
|
||||
return autoConvert_identity_KubernetesUserInfo_To_v1alpha1_KubernetesUserInfo(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_UserInfo_To_identity_UserInfo(in *UserInfo, out *identity.UserInfo, s conversion.Scope) error {
|
||||
out.Username = in.Username
|
||||
out.UID = in.UID
|
||||
out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups))
|
||||
out.Extra = *(*map[string]identity.ExtraValue)(unsafe.Pointer(&in.Extra))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_UserInfo_To_identity_UserInfo is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_UserInfo_To_identity_UserInfo(in *UserInfo, out *identity.UserInfo, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_UserInfo_To_identity_UserInfo(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_identity_UserInfo_To_v1alpha1_UserInfo(in *identity.UserInfo, out *UserInfo, s conversion.Scope) error {
|
||||
out.Username = in.Username
|
||||
out.UID = in.UID
|
||||
out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups))
|
||||
out.Extra = *(*map[string]ExtraValue)(unsafe.Pointer(&in.Extra))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_identity_UserInfo_To_v1alpha1_UserInfo is an autogenerated conversion function.
|
||||
func Convert_identity_UserInfo_To_v1alpha1_UserInfo(in *identity.UserInfo, out *UserInfo, s conversion.Scope) error {
|
||||
return autoConvert_identity_UserInfo_To_v1alpha1_UserInfo(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_WhoAmIRequest_To_identity_WhoAmIRequest(in *WhoAmIRequest, out *identity.WhoAmIRequest, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
if err := Convert_v1alpha1_WhoAmIRequestSpec_To_identity_WhoAmIRequestSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_v1alpha1_WhoAmIRequestStatus_To_identity_WhoAmIRequestStatus(&in.Status, &out.Status, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_WhoAmIRequest_To_identity_WhoAmIRequest is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_WhoAmIRequest_To_identity_WhoAmIRequest(in *WhoAmIRequest, out *identity.WhoAmIRequest, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_WhoAmIRequest_To_identity_WhoAmIRequest(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_identity_WhoAmIRequest_To_v1alpha1_WhoAmIRequest(in *identity.WhoAmIRequest, out *WhoAmIRequest, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
if err := Convert_identity_WhoAmIRequestSpec_To_v1alpha1_WhoAmIRequestSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_identity_WhoAmIRequestStatus_To_v1alpha1_WhoAmIRequestStatus(&in.Status, &out.Status, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_identity_WhoAmIRequest_To_v1alpha1_WhoAmIRequest is an autogenerated conversion function.
|
||||
func Convert_identity_WhoAmIRequest_To_v1alpha1_WhoAmIRequest(in *identity.WhoAmIRequest, out *WhoAmIRequest, s conversion.Scope) error {
|
||||
return autoConvert_identity_WhoAmIRequest_To_v1alpha1_WhoAmIRequest(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_WhoAmIRequestList_To_identity_WhoAmIRequestList(in *WhoAmIRequestList, out *identity.WhoAmIRequestList, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
out.Items = *(*[]identity.WhoAmIRequest)(unsafe.Pointer(&in.Items))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_WhoAmIRequestList_To_identity_WhoAmIRequestList is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_WhoAmIRequestList_To_identity_WhoAmIRequestList(in *WhoAmIRequestList, out *identity.WhoAmIRequestList, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_WhoAmIRequestList_To_identity_WhoAmIRequestList(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_identity_WhoAmIRequestList_To_v1alpha1_WhoAmIRequestList(in *identity.WhoAmIRequestList, out *WhoAmIRequestList, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
out.Items = *(*[]WhoAmIRequest)(unsafe.Pointer(&in.Items))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_identity_WhoAmIRequestList_To_v1alpha1_WhoAmIRequestList is an autogenerated conversion function.
|
||||
func Convert_identity_WhoAmIRequestList_To_v1alpha1_WhoAmIRequestList(in *identity.WhoAmIRequestList, out *WhoAmIRequestList, s conversion.Scope) error {
|
||||
return autoConvert_identity_WhoAmIRequestList_To_v1alpha1_WhoAmIRequestList(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_WhoAmIRequestSpec_To_identity_WhoAmIRequestSpec(in *WhoAmIRequestSpec, out *identity.WhoAmIRequestSpec, s conversion.Scope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_WhoAmIRequestSpec_To_identity_WhoAmIRequestSpec is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_WhoAmIRequestSpec_To_identity_WhoAmIRequestSpec(in *WhoAmIRequestSpec, out *identity.WhoAmIRequestSpec, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_WhoAmIRequestSpec_To_identity_WhoAmIRequestSpec(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_identity_WhoAmIRequestSpec_To_v1alpha1_WhoAmIRequestSpec(in *identity.WhoAmIRequestSpec, out *WhoAmIRequestSpec, s conversion.Scope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_identity_WhoAmIRequestSpec_To_v1alpha1_WhoAmIRequestSpec is an autogenerated conversion function.
|
||||
func Convert_identity_WhoAmIRequestSpec_To_v1alpha1_WhoAmIRequestSpec(in *identity.WhoAmIRequestSpec, out *WhoAmIRequestSpec, s conversion.Scope) error {
|
||||
return autoConvert_identity_WhoAmIRequestSpec_To_v1alpha1_WhoAmIRequestSpec(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_WhoAmIRequestStatus_To_identity_WhoAmIRequestStatus(in *WhoAmIRequestStatus, out *identity.WhoAmIRequestStatus, s conversion.Scope) error {
|
||||
if err := Convert_v1alpha1_KubernetesUserInfo_To_identity_KubernetesUserInfo(&in.KubernetesUserInfo, &out.KubernetesUserInfo, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_WhoAmIRequestStatus_To_identity_WhoAmIRequestStatus is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_WhoAmIRequestStatus_To_identity_WhoAmIRequestStatus(in *WhoAmIRequestStatus, out *identity.WhoAmIRequestStatus, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_WhoAmIRequestStatus_To_identity_WhoAmIRequestStatus(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_identity_WhoAmIRequestStatus_To_v1alpha1_WhoAmIRequestStatus(in *identity.WhoAmIRequestStatus, out *WhoAmIRequestStatus, s conversion.Scope) error {
|
||||
if err := Convert_identity_KubernetesUserInfo_To_v1alpha1_KubernetesUserInfo(&in.KubernetesUserInfo, &out.KubernetesUserInfo, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_identity_WhoAmIRequestStatus_To_v1alpha1_WhoAmIRequestStatus is an autogenerated conversion function.
|
||||
func Convert_identity_WhoAmIRequestStatus_To_v1alpha1_WhoAmIRequestStatus(in *identity.WhoAmIRequestStatus, out *WhoAmIRequestStatus, s conversion.Scope) error {
|
||||
return autoConvert_identity_WhoAmIRequestStatus_To_v1alpha1_WhoAmIRequestStatus(in, out, s)
|
||||
}
|
||||
184
generated/1.17/apis/concierge/identity/v1alpha1/zz_generated.deepcopy.go
generated
Normal file
184
generated/1.17/apis/concierge/identity/v1alpha1/zz_generated.deepcopy.go
generated
Normal file
@@ -0,0 +1,184 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ExtraValue) DeepCopyInto(out *ExtraValue) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ExtraValue, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraValue.
|
||||
func (in ExtraValue) DeepCopy() ExtraValue {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExtraValue)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesUserInfo) DeepCopyInto(out *KubernetesUserInfo) {
|
||||
*out = *in
|
||||
in.User.DeepCopyInto(&out.User)
|
||||
if in.Audiences != nil {
|
||||
in, out := &in.Audiences, &out.Audiences
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesUserInfo.
|
||||
func (in *KubernetesUserInfo) DeepCopy() *KubernetesUserInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesUserInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UserInfo) DeepCopyInto(out *UserInfo) {
|
||||
*out = *in
|
||||
if in.Groups != nil {
|
||||
in, out := &in.Groups, &out.Groups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Extra != nil {
|
||||
in, out := &in.Extra, &out.Extra
|
||||
*out = make(map[string]ExtraValue, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal []string
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = make(ExtraValue, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserInfo.
|
||||
func (in *UserInfo) DeepCopy() *UserInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(UserInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WhoAmIRequest) DeepCopyInto(out *WhoAmIRequest) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WhoAmIRequest.
|
||||
func (in *WhoAmIRequest) DeepCopy() *WhoAmIRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WhoAmIRequest)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *WhoAmIRequest) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WhoAmIRequestList) DeepCopyInto(out *WhoAmIRequestList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]WhoAmIRequest, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WhoAmIRequestList.
|
||||
func (in *WhoAmIRequestList) DeepCopy() *WhoAmIRequestList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WhoAmIRequestList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *WhoAmIRequestList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WhoAmIRequestSpec) DeepCopyInto(out *WhoAmIRequestSpec) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WhoAmIRequestSpec.
|
||||
func (in *WhoAmIRequestSpec) DeepCopy() *WhoAmIRequestSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WhoAmIRequestSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WhoAmIRequestStatus) DeepCopyInto(out *WhoAmIRequestStatus) {
|
||||
*out = *in
|
||||
in.KubernetesUserInfo.DeepCopyInto(&out.KubernetesUserInfo)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WhoAmIRequestStatus.
|
||||
func (in *WhoAmIRequestStatus) DeepCopy() *WhoAmIRequestStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WhoAmIRequestStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
19
generated/1.17/apis/concierge/identity/v1alpha1/zz_generated.defaults.go
generated
Normal file
19
generated/1.17/apis/concierge/identity/v1alpha1/zz_generated.defaults.go
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
return nil
|
||||
}
|
||||
14
generated/1.17/apis/concierge/identity/validation/validation.go
generated
Normal file
14
generated/1.17/apis/concierge/identity/validation/validation.go
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
identityapi "go.pinniped.dev/generated/1.17/apis/concierge/identity"
|
||||
)
|
||||
|
||||
func ValidateWhoAmIRequest(whoAmIRequest *identityapi.WhoAmIRequest) field.ErrorList {
|
||||
return nil // add validation for spec here if we expand it
|
||||
}
|
||||
184
generated/1.17/apis/concierge/identity/zz_generated.deepcopy.go
generated
Normal file
184
generated/1.17/apis/concierge/identity/zz_generated.deepcopy.go
generated
Normal file
@@ -0,0 +1,184 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package identity
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ExtraValue) DeepCopyInto(out *ExtraValue) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ExtraValue, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraValue.
|
||||
func (in ExtraValue) DeepCopy() ExtraValue {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExtraValue)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesUserInfo) DeepCopyInto(out *KubernetesUserInfo) {
|
||||
*out = *in
|
||||
in.User.DeepCopyInto(&out.User)
|
||||
if in.Audiences != nil {
|
||||
in, out := &in.Audiences, &out.Audiences
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesUserInfo.
|
||||
func (in *KubernetesUserInfo) DeepCopy() *KubernetesUserInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesUserInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UserInfo) DeepCopyInto(out *UserInfo) {
|
||||
*out = *in
|
||||
if in.Groups != nil {
|
||||
in, out := &in.Groups, &out.Groups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Extra != nil {
|
||||
in, out := &in.Extra, &out.Extra
|
||||
*out = make(map[string]ExtraValue, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal []string
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = make(ExtraValue, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserInfo.
|
||||
func (in *UserInfo) DeepCopy() *UserInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(UserInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WhoAmIRequest) DeepCopyInto(out *WhoAmIRequest) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WhoAmIRequest.
|
||||
func (in *WhoAmIRequest) DeepCopy() *WhoAmIRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WhoAmIRequest)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *WhoAmIRequest) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WhoAmIRequestList) DeepCopyInto(out *WhoAmIRequestList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]WhoAmIRequest, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WhoAmIRequestList.
|
||||
func (in *WhoAmIRequestList) DeepCopy() *WhoAmIRequestList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WhoAmIRequestList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *WhoAmIRequestList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WhoAmIRequestSpec) DeepCopyInto(out *WhoAmIRequestSpec) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WhoAmIRequestSpec.
|
||||
func (in *WhoAmIRequestSpec) DeepCopy() *WhoAmIRequestSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WhoAmIRequestSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WhoAmIRequestStatus) DeepCopyInto(out *WhoAmIRequestStatus) {
|
||||
*out = *in
|
||||
in.KubernetesUserInfo.DeepCopyInto(&out.KubernetesUserInfo)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WhoAmIRequestStatus.
|
||||
func (in *WhoAmIRequestStatus) DeepCopy() *WhoAmIRequestStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WhoAmIRequestStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -31,6 +31,7 @@ type TokenCredentialRequestStatus struct {
|
||||
// TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchange for a cluster-specific credential.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +genclient:onlyVerbs=create
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type TokenCredentialRequest struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by conversion-gen. DO NOT EDIT.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
@@ -32,6 +32,10 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&OIDCIdentityProvider{},
|
||||
&OIDCIdentityProviderList{},
|
||||
&LDAPIdentityProvider{},
|
||||
&LDAPIdentityProviderList{},
|
||||
&ActiveDirectoryIdentityProvider{},
|
||||
&ActiveDirectoryIdentityProviderList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
||||
182
generated/1.17/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go
generated
Normal file
182
generated/1.17/apis/supervisor/idp/v1alpha1/types_activedirectoryidentityprovider.go
generated
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ActiveDirectoryIdentityProviderPhase string
|
||||
|
||||
const (
|
||||
// ActiveDirectoryPhasePending is the default phase for newly-created ActiveDirectoryIdentityProvider resources.
|
||||
ActiveDirectoryPhasePending ActiveDirectoryIdentityProviderPhase = "Pending"
|
||||
|
||||
// ActiveDirectoryPhaseReady is the phase for an ActiveDirectoryIdentityProvider resource in a healthy state.
|
||||
ActiveDirectoryPhaseReady ActiveDirectoryIdentityProviderPhase = "Ready"
|
||||
|
||||
// ActiveDirectoryPhaseError is the phase for an ActiveDirectoryIdentityProvider in an unhealthy state.
|
||||
ActiveDirectoryPhaseError ActiveDirectoryIdentityProviderPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of an Active Directory identity provider.
|
||||
type ActiveDirectoryIdentityProviderStatus struct {
|
||||
// Phase summarizes the overall status of the ActiveDirectoryIdentityProvider.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase ActiveDirectoryIdentityProviderPhase `json:"phase,omitempty"`
|
||||
|
||||
// Represents the observations of an identity provider's current state.
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderBind struct {
|
||||
// SecretName contains the name of a namespace-local Secret object that provides the username and
|
||||
// password for an Active Directory bind user. This account will be used to perform LDAP searches. The Secret should be
|
||||
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
|
||||
// should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
|
||||
// The password must be non-empty.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
SecretName string `json:"secretName"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderUserSearchAttributes struct {
|
||||
// Username specifies the name of the attribute in Active Directory entry whose value shall become the username
|
||||
// of the user after a successful authentication.
|
||||
// Optional, when empty this defaults to "userPrincipalName".
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
|
||||
// UID specifies the name of the attribute in the ActiveDirectory entry which whose value shall be used to uniquely
|
||||
// identify the user within this ActiveDirectory provider after a successful authentication.
|
||||
// Optional, when empty this defaults to "objectGUID".
|
||||
// +optional
|
||||
UID string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderGroupSearchAttributes struct {
|
||||
// GroupName specifies the name of the attribute in the Active Directory entries whose value shall become a group name
|
||||
// in the user's list of groups after a successful authentication.
|
||||
// The value of this field is case-sensitive and must match the case of the attribute name returned by the ActiveDirectory
|
||||
// server in the user's entry. E.g. "cn" for common name. Distinguished names can be used by specifying lower-case "dn".
|
||||
// Optional. When not specified, this defaults to a custom field that looks like "sAMAccountName@domain",
|
||||
// where domain is constructed from the domain components of the group DN.
|
||||
// +optional
|
||||
GroupName string `json:"groupName,omitempty"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderUserSearch struct {
|
||||
// Base is the dn (distinguished name) that should be used as the search base when searching for users.
|
||||
// E.g. "ou=users,dc=example,dc=com".
|
||||
// Optional, when not specified it will be based on the result of a query for the defaultNamingContext
|
||||
// (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
// The default behavior searches your entire domain for users.
|
||||
// It may make sense to specify a subtree as a search base if you wish to exclude some users
|
||||
// or to make searches faster.
|
||||
// +optional
|
||||
Base string `json:"base,omitempty"`
|
||||
|
||||
// Filter is the search filter which should be applied when searching for users. The pattern "{}" must occur
|
||||
// in the filter at least once and will be dynamically replaced by the username for which the search is being run.
|
||||
// E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see
|
||||
// https://ldap.com/ldap-filters.
|
||||
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
|
||||
// Optional. When not specified, the default will be
|
||||
// '(&(objectClass=person)(!(objectClass=computer))(!(showInAdvancedViewOnly=TRUE))(|(sAMAccountName={}")(mail={})(userPrincipalName={})(sAMAccountType=805306368))'
|
||||
// This means that the user is a person, is not a computer, the sAMAccountType is for a normal user account,
|
||||
// and is not shown in advanced view only
|
||||
// (which would likely mean its a system created service account with advanced permissions).
|
||||
// Also, either the sAMAccountName, the userPrincipalName, or the mail attribute matches the input username.
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// Attributes specifies how the user's information should be read from the ActiveDirectory entry which was found as
|
||||
// the result of the user search.
|
||||
// +optional
|
||||
Attributes ActiveDirectoryIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
type ActiveDirectoryIdentityProviderGroupSearch struct {
|
||||
// Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g.
|
||||
// "ou=groups,dc=example,dc=com".
|
||||
// Optional, when not specified it will be based on the result of a query for the defaultNamingContext
|
||||
// (see https://docs.microsoft.com/en-us/windows/win32/adschema/rootdse).
|
||||
// The default behavior searches your entire domain for groups.
|
||||
// It may make sense to specify a subtree as a search base if you wish to exclude some groups
|
||||
// for security reasons or to make searches faster.
|
||||
// +optional
|
||||
Base string `json:"base,omitempty"`
|
||||
|
||||
// Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user.
|
||||
// The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the
|
||||
// dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or
|
||||
// "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see
|
||||
// https://ldap.com/ldap-filters.
|
||||
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
|
||||
// Optional. When not specified, the default will act as if the filter were specified as
|
||||
// "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})".
|
||||
// This searches nested groups by default.
|
||||
// Note that nested group search can be slow for some Active Directory servers. To disable it,
|
||||
// you can set the filter to
|
||||
// "(&(objectClass=group)(member={})"
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as
|
||||
// the result of the group search.
|
||||
// +optional
|
||||
Attributes ActiveDirectoryIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring an ActiveDirectory identity provider.
|
||||
type ActiveDirectoryIdentityProviderSpec struct {
|
||||
// Host is the hostname of this Active Directory identity provider, i.e., where to connect. For example: ldap.example.com:636.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Host string `json:"host"`
|
||||
|
||||
// TLS contains the connection settings for how to establish the connection to the Host.
|
||||
TLS *TLSSpec `json:"tls,omitempty"`
|
||||
|
||||
// Bind contains the configuration for how to provide access credentials during an initial bind to the ActiveDirectory server
|
||||
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
|
||||
Bind ActiveDirectoryIdentityProviderBind `json:"bind,omitempty"`
|
||||
|
||||
// UserSearch contains the configuration for searching for a user by name in Active Directory.
|
||||
UserSearch ActiveDirectoryIdentityProviderUserSearch `json:"userSearch,omitempty"`
|
||||
|
||||
// GroupSearch contains the configuration for searching for a user's group membership in ActiveDirectory.
|
||||
GroupSearch ActiveDirectoryIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
|
||||
}
|
||||
|
||||
// ActiveDirectoryIdentityProvider describes the configuration of an upstream Microsoft Active Directory identity provider.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
|
||||
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
|
||||
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type ActiveDirectoryIdentityProvider struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec for configuring the identity provider.
|
||||
Spec ActiveDirectoryIdentityProviderSpec `json:"spec"`
|
||||
|
||||
// Status of the identity provider.
|
||||
Status ActiveDirectoryIdentityProviderStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// List of ActiveDirectoryIdentityProvider objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type ActiveDirectoryIdentityProviderList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []ActiveDirectoryIdentityProvider `json:"items"`
|
||||
}
|
||||
171
generated/1.17/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go
generated
Normal file
171
generated/1.17/apis/supervisor/idp/v1alpha1/types_ldapidentityprovider.go
generated
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type LDAPIdentityProviderPhase string
|
||||
|
||||
const (
|
||||
// LDAPPhasePending is the default phase for newly-created LDAPIdentityProvider resources.
|
||||
LDAPPhasePending LDAPIdentityProviderPhase = "Pending"
|
||||
|
||||
// LDAPPhaseReady is the phase for an LDAPIdentityProvider resource in a healthy state.
|
||||
LDAPPhaseReady LDAPIdentityProviderPhase = "Ready"
|
||||
|
||||
// LDAPPhaseError is the phase for an LDAPIdentityProvider in an unhealthy state.
|
||||
LDAPPhaseError LDAPIdentityProviderPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of an LDAP identity provider.
|
||||
type LDAPIdentityProviderStatus struct {
|
||||
// Phase summarizes the overall status of the LDAPIdentityProvider.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase LDAPIdentityProviderPhase `json:"phase,omitempty"`
|
||||
|
||||
// Represents the observations of an identity provider's current state.
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
type LDAPIdentityProviderBind struct {
|
||||
// SecretName contains the name of a namespace-local Secret object that provides the username and
|
||||
// password for an LDAP bind user. This account will be used to perform LDAP searches. The Secret should be
|
||||
// of type "kubernetes.io/basic-auth" which includes "username" and "password" keys. The username value
|
||||
// should be the full dn (distinguished name) of your bind account, e.g. "cn=bind-account,ou=users,dc=example,dc=com".
|
||||
// The password must be non-empty.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
SecretName string `json:"secretName"`
|
||||
}
|
||||
|
||||
type LDAPIdentityProviderUserSearchAttributes struct {
|
||||
// Username specifies the name of the attribute in the LDAP entry whose value shall become the username
|
||||
// of the user after a successful authentication. This would typically be the same attribute name used in
|
||||
// the user search filter, although it can be different. E.g. "mail" or "uid" or "userPrincipalName".
|
||||
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
|
||||
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn". When this field
|
||||
// is set to "dn" then the LDAPIdentityProviderUserSearch's Filter field cannot be blank, since the default
|
||||
// value of "dn={}" would not work.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Username string `json:"username,omitempty"`
|
||||
|
||||
// UID specifies the name of the attribute in the LDAP entry which whose value shall be used to uniquely
|
||||
// identify the user within this LDAP provider after a successful authentication. E.g. "uidNumber" or "objectGUID".
|
||||
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
|
||||
// server in the user's entry. Distinguished names can be used by specifying lower-case "dn".
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
UID string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
type LDAPIdentityProviderGroupSearchAttributes struct {
|
||||
// GroupName specifies the name of the attribute in the LDAP entries whose value shall become a group name
|
||||
// in the user's list of groups after a successful authentication.
|
||||
// The value of this field is case-sensitive and must match the case of the attribute name returned by the LDAP
|
||||
// server in the user's entry. E.g. "cn" for common name. Distinguished names can be used by specifying lower-case "dn".
|
||||
// Optional. When not specified, the default will act as if the GroupName were specified as "dn" (distinguished name).
|
||||
// +optional
|
||||
GroupName string `json:"groupName,omitempty"`
|
||||
}
|
||||
|
||||
type LDAPIdentityProviderUserSearch struct {
|
||||
// Base is the dn (distinguished name) that should be used as the search base when searching for users.
|
||||
// E.g. "ou=users,dc=example,dc=com".
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Base string `json:"base,omitempty"`
|
||||
|
||||
// Filter is the LDAP search filter which should be applied when searching for users. The pattern "{}" must occur
|
||||
// in the filter at least once and will be dynamically replaced by the username for which the search is being run.
|
||||
// E.g. "mail={}" or "&(objectClass=person)(uid={})". For more information about LDAP filters, see
|
||||
// https://ldap.com/ldap-filters.
|
||||
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
|
||||
// Optional. When not specified, the default will act as if the Filter were specified as the value from
|
||||
// Attributes.Username appended by "={}". When the Attributes.Username is set to "dn" then the Filter must be
|
||||
// explicitly specified, since the default value of "dn={}" would not work.
|
||||
// +optional
|
||||
Filter string `json:"filter,omitempty"`
|
||||
|
||||
// Attributes specifies how the user's information should be read from the LDAP entry which was found as
|
||||
// the result of the user search.
|
||||
// +optional
|
||||
Attributes LDAPIdentityProviderUserSearchAttributes `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
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.
|
||||
// +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.
|
||||
// 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"`
|
||||
|
||||
// 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
|
||||
Attributes LDAPIdentityProviderGroupSearchAttributes `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring an LDAP identity provider.
|
||||
type LDAPIdentityProviderSpec struct {
|
||||
// Host is the hostname of this LDAP identity provider, i.e., where to connect. For example: ldap.example.com:636.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
Host string `json:"host"`
|
||||
|
||||
// TLS contains the connection settings for how to establish the connection to the Host.
|
||||
TLS *TLSSpec `json:"tls,omitempty"`
|
||||
|
||||
// Bind contains the configuration for how to provide access credentials during an initial bind to the LDAP server
|
||||
// to be allowed to perform searches and binds to validate a user's credentials during a user's authentication attempt.
|
||||
Bind LDAPIdentityProviderBind `json:"bind,omitempty"`
|
||||
|
||||
// UserSearch contains the configuration for searching for a user by name in the LDAP provider.
|
||||
UserSearch LDAPIdentityProviderUserSearch `json:"userSearch,omitempty"`
|
||||
|
||||
// GroupSearch contains the configuration for searching for a user's group membership in the LDAP provider.
|
||||
GroupSearch LDAPIdentityProviderGroupSearch `json:"groupSearch,omitempty"`
|
||||
}
|
||||
|
||||
// LDAPIdentityProvider describes the configuration of an upstream Lightweight Directory Access
|
||||
// Protocol (LDAP) identity provider.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
|
||||
// +kubebuilder:printcolumn:name="Host",type=string,JSONPath=`.spec.host`
|
||||
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
// +kubebuilder:subresource:status
|
||||
type LDAPIdentityProvider struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec for configuring the identity provider.
|
||||
Spec LDAPIdentityProviderSpec `json:"spec"`
|
||||
|
||||
// Status of the identity provider.
|
||||
Status LDAPIdentityProviderStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// List of LDAPIdentityProvider objects.
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type LDAPIdentityProviderList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []LDAPIdentityProvider `json:"items"`
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user