mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-28 00:22:27 +00:00
Compare commits
582 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ab94b97f4a | ||
|
|
d6a172214d | ||
|
|
638fa7ba27 | ||
|
|
b5ffab6330 | ||
|
|
8556a638a2 | ||
|
|
44c7f8daf0 | ||
|
|
1efa4da80c | ||
|
|
62785674c3 | ||
|
|
9e4f601a3f | ||
|
|
bb7e7fe81e | ||
|
|
bed2d2dd62 | ||
|
|
90b2854032 | ||
|
|
96fda6ed13 | ||
|
|
67a568811a | ||
|
|
620a4d55b7 | ||
|
|
a52872cd03 | ||
|
|
0dfb3e95c5 | ||
|
|
e532a88647 | ||
|
|
54a8297cc4 | ||
|
|
2843c4f8cb | ||
|
|
cc51c72c12 | ||
|
|
0ab9927115 | ||
|
|
204c8e8dbc | ||
|
|
638d9235a2 | ||
|
|
81a4c84f46 | ||
|
|
9f509d3f13 | ||
|
|
5f3eab2538 | ||
|
|
c45d48d027 | ||
|
|
09560fd8dc | ||
|
|
264778113d | ||
|
|
b5889f37ff | ||
|
|
45e4695444 | ||
|
|
6a21499ed3 | ||
|
|
211d4fd0b6 | ||
|
|
8ffd9fdc4e | ||
|
|
d76ac56df2 | ||
|
|
d86b24ca2f | ||
|
|
73716f1b91 | ||
|
|
521adffb17 | ||
|
|
70d607d87e | ||
|
|
9dfa1f5ee5 | ||
|
|
f63ded99bc | ||
|
|
e7b7b597ff | ||
|
|
e5da119000 | ||
|
|
923938ab26 | ||
|
|
352d4dc5b1 | ||
|
|
dab7b57da0 | ||
|
|
12d35583c5 | ||
|
|
599c537d24 | ||
|
|
38f3ea3f2f | ||
|
|
e450a348c5 | ||
|
|
11d820be06 | ||
|
|
63816aa3ba | ||
|
|
e5314164c5 | ||
|
|
abf606ab72 | ||
|
|
b59a4f3fec | ||
|
|
3b461572ea | ||
|
|
271c006b6c | ||
|
|
043cefcd9f | ||
|
|
2296faaeef | ||
|
|
fec24d307e | ||
|
|
64b13043ed | ||
|
|
5501b5aa13 | ||
|
|
9450048acf | ||
|
|
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 | ||
|
|
f015ad5852 | ||
|
|
b04fd46319 | ||
|
|
4c304e4224 | ||
|
|
0a9f446893 | ||
|
|
96cec59236 | ||
|
|
4faf724c2c | ||
|
|
de88ae2f61 | ||
|
|
dd3d1c8b1b | ||
|
|
2e9baf9fa6 | ||
|
|
ac01186499 | ||
|
|
2eb01bd307 | ||
|
|
741b8fe88d | ||
|
|
d25c6d9d0a | ||
|
|
89b00e3702 | ||
|
|
d2480e6300 | ||
|
|
4205e3dedc | ||
|
|
ee80920ffd | ||
|
|
45f4a0528c | ||
|
|
d0266cecdb | ||
|
|
0fc1f17866 | ||
|
|
ae6503e972 | ||
|
|
44b7679e9f | ||
|
|
12d5b8959d | ||
|
|
5b076e7421 | ||
|
|
1ffe70bbea | ||
|
|
e4c49c37b9 | ||
|
|
268ca5b7f6 | ||
|
|
cf735715f6 | ||
|
|
2679d27ced | ||
|
|
6b71b8d8ad | ||
|
|
8697488126 | ||
|
|
dfcc2a1eb8 | ||
|
|
812f5084a1 | ||
|
|
43da4ab2e0 | ||
|
|
e4d8af6701 | ||
|
|
d06c935c2c | ||
|
|
9399b5d800 | ||
|
|
05a471fdf9 | ||
|
|
81d4e50f94 | ||
|
|
850f030fe3 | ||
|
|
f7958ae75b | ||
|
|
ee05f155ca | ||
|
|
2ae631b603 | ||
|
|
9c64476aee | ||
|
|
b6e98b5783 | ||
|
|
9addb4d6e0 | ||
|
|
2a921f7090 | ||
|
|
bb8b65cca6 | ||
|
|
5c331e9002 | ||
|
|
1382fc6e5f | ||
|
|
cc8c917249 | ||
|
|
ae498f14b4 | ||
|
|
288d9c999e | ||
|
|
26922307ad | ||
|
|
5549a262b9 | ||
|
|
6b46bae6c6 | ||
|
|
c5df66fbd5 | ||
|
|
23e8c35918 | ||
|
|
ab60396ac4 | ||
|
|
343c275f46 | ||
|
|
12e41d783f | ||
|
|
2f891b4bfb | ||
|
|
170b86d0c6 | ||
|
|
07b7b743b4 | ||
|
|
64aff7b983 | ||
|
|
1299231a48 | ||
|
|
b6abb022f6 | ||
|
|
300d7bd99c | ||
|
|
012bebd66e | ||
|
|
e1d06ce4d8 | ||
|
|
52b98bdb87 | ||
|
|
62c117421a | ||
|
|
efe1fa89fe | ||
|
|
93d25a349f | ||
|
|
93ebd0f949 | ||
|
|
74a8005f92 | ||
|
|
5b4e58f0b8 | ||
|
|
b871a02ca3 | ||
|
|
6a20bbf607 | ||
|
|
dfa4d639e6 | ||
|
|
8b4024bf82 | ||
|
|
d89c6546e7 | ||
|
|
2710591429 | ||
|
|
02815cfb26 | ||
|
|
3f7cb5d9f8 | ||
|
|
46ad41e813 | ||
|
|
d4eca3a82a | ||
|
|
c03a088399 | ||
|
|
f81dda4eda | ||
|
|
1ceef5874e | ||
|
|
1b224bc4f2 | ||
|
|
530d6961c2 | ||
|
|
fe500882ef | ||
|
|
8358c26107 | ||
|
|
ad9a187522 | ||
|
|
8a41419b94 | ||
|
|
6ef7ec21cd | ||
|
|
df1d15ebd1 | ||
|
|
b3732e8b6c | ||
|
|
7e887666ce | ||
|
|
d6e6f51ced | ||
|
|
9e21de9c47 | ||
|
|
04c4cd9534 | ||
|
|
5821faec03 | ||
|
|
8bca244d59 | ||
|
|
79fa96cfbc | ||
|
|
b5cbe018e3 | ||
|
|
33f4b671d1 | ||
|
|
50c3e4c00f | ||
|
|
5486427d88 | ||
|
|
906bfa023c | ||
|
|
1c3518e18a | ||
|
|
88fd9e5c5e | ||
|
|
616211c1bc | ||
|
|
7a9c0e8c69 | ||
|
|
c09020102c | ||
|
|
af11d8cd58 | ||
|
|
93ba1b54f2 | ||
|
|
792bb98680 |
12
.dockerignore
Normal file
12
.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
||||
./.*
|
||||
./*.md
|
||||
./*.yaml
|
||||
./apis
|
||||
./deploy
|
||||
./Dockerfile
|
||||
./generated/1.1*
|
||||
./internal/mocks
|
||||
./LICENSE
|
||||
./site/
|
||||
./test
|
||||
**/*_test.go
|
||||
15
.github/codecov.yml
vendored
Normal file
15
.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
codecov:
|
||||
strict_yaml_branch: main
|
||||
require_ci_to_pass: no
|
||||
notify:
|
||||
wait_for_ci: no
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
ignore:
|
||||
- cmd/local-user-authenticator/
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -14,8 +14,8 @@
|
||||
# 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
- id: validate-copyright-year
|
||||
name: Validate copyright year
|
||||
entry: hack/check-copyright-year.sh
|
||||
language: script
|
||||
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,24 +8,17 @@ 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.
|
||||
|
||||
## Meeting with the Maintainers
|
||||
## Community Meetings
|
||||
|
||||
The maintainers aspire to hold a video conference every other week with the Pinniped community.
|
||||
Any community member may request to add topics to the agenda by contacting a [maintainer](MAINTAINERS.md)
|
||||
in advance, or by attending and raising the topic during time remaining after the agenda is covered.
|
||||
Typical agenda items include topics regarding the roadmap, feature requests, bug reports, pull requests, etc.
|
||||
A [public document](https://docs.google.com/document/d/1qYA35wZV-6bxcH5375vOnIGkNBo7e4OROgsV4Sj8WjQ)
|
||||
tracks the agendas and notes for these 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.
|
||||
|
||||
These meetings are currently scheduled for the first and third Thursday mornings of each month
|
||||
at 9 AM Pacific Time, using this [Zoom meeting](https://VMware.zoom.us/j/94638309756?pwd=V3NvRXJIdDg5QVc0TUdFM2dYRzgrUT09).
|
||||
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.
|
||||
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).
|
||||
|
||||
## Issues
|
||||
|
||||
@@ -103,36 +96,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
|
||||
|
||||
51
Dockerfile
51
Dockerfile
@@ -1,36 +1,41 @@
|
||||
# syntax = docker/dockerfile:1.0-experimental
|
||||
|
||||
# Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
FROM golang:1.15.12 as build-env
|
||||
FROM golang:1.16.4 as build-env
|
||||
|
||||
WORKDIR /work
|
||||
# Get dependencies first so they can be cached as a layer
|
||||
COPY go.* ./
|
||||
COPY generated/1.20/apis/go.* ./generated/1.20/apis/
|
||||
COPY generated/1.20/client/go.* ./generated/1.20/client/
|
||||
RUN go mod download
|
||||
|
||||
# Copy only the production source code to avoid cache misses when editing other files
|
||||
COPY generated ./generated
|
||||
COPY cmd ./cmd
|
||||
COPY pkg ./pkg
|
||||
COPY internal ./internal
|
||||
COPY hack ./hack
|
||||
COPY . .
|
||||
ARG GOPROXY
|
||||
|
||||
# Build the executable binary (CGO_ENABLED=0 means static linking)
|
||||
RUN mkdir out \
|
||||
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(hack/get-ldflags.sh)" -o out ./cmd/pinniped-concierge/... \
|
||||
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(hack/get-ldflags.sh)" -o out ./cmd/pinniped-supervisor/... \
|
||||
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out ./cmd/local-user-authenticator/...
|
||||
# Pass in GOCACHE (build cache) and GOMODCACHE (module cache) so they
|
||||
# can be re-used between image builds.
|
||||
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/...
|
||||
|
||||
# Use a runtime image based on Debian slim
|
||||
# Use a Debian slim image to grab a reasonable default CA bundle.
|
||||
FROM debian:10.9-slim AS get-ca-bundle-env
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* /var/cache/debconf/*
|
||||
|
||||
# Use a runtime image based on Debian slim.
|
||||
FROM debian:10.9-slim
|
||||
RUN apt-get update && apt-get install -y ca-certificates procps && rm -rf /var/lib/apt/lists/*
|
||||
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/pinniped-concierge /usr/local/bin/pinniped-concierge
|
||||
COPY --from=build-env /work/out/pinniped-supervisor /usr/local/bin/pinniped-supervisor
|
||||
COPY --from=build-env /work/out/local-user-authenticator /usr/local/bin/local-user-authenticator
|
||||
# Copy the binaries from the build-env stage.
|
||||
COPY --from=build-env /work/out/ /usr/local/bin/
|
||||
|
||||
# Document the ports
|
||||
EXPOSE 8080 8443
|
||||
|
||||
18
README.md
18
README.md
@@ -10,7 +10,7 @@ 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.
|
||||
|
||||
### Example Use Cases
|
||||
### Example use cases
|
||||
|
||||
* Your team uses a large enterprise IDP, and has many clusters that they
|
||||
manage. Pinniped provides:
|
||||
@@ -39,23 +39,27 @@ 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/).
|
||||
To learn more, see [architecture](https://pinniped.dev/docs/background/architecture/).
|
||||
|
||||
<img src="site/content/docs/img/pinniped_architecture_concierge_supervisor.svg" alt="Pinniped Architecture Sketch"/>
|
||||
## Getting started with Pinniped
|
||||
|
||||
## Trying Pinniped
|
||||
Care to kick the tires? It's easy to [install and try Pinniped](https://pinniped.dev/docs/).
|
||||
|
||||
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, 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.
|
||||
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).
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome. Before contributing, please see the [contributing guide](CONTRIBUTING.md).
|
||||
|
||||
## Reporting Security Vulnerabilities
|
||||
## Reporting security vulnerabilities
|
||||
|
||||
Please follow the procedure described in [SECURITY.md](SECURITY.md).
|
||||
|
||||
|
||||
50
ROADMAP.md
Normal file
50
ROADMAP.md
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
## **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: April 2021
|
||||
Theme|Description|Timeline|
|
||||
|--|--|--|
|
||||
|LDAP Support|Extends upstream IDP protocols|May 2021|
|
||||
|Improved Documentation|Reorganizing and improving Pinniped docs; new how-to guides and tutorials|May 2021|
|
||||
|Multiple IDPs|Support for multiple upstream IDPs to be configured simultaneously|Jun 2021|
|
||||
|Wider Concierge cluster support|Support for more cluster types in the Concierge|Jul 2021|
|
||||
|Improving Security Posture|Offer the best security posture for Kubernetes cluster authentication|Exploring/Ongoing|
|
||||
|Improve our CI/CD systems|Upgrade tests; make Kind more efficient and reliable for CI ; Windows tests; performance tests; scale tests; soak tests|Exploring/Ongoing|
|
||||
|CLI Improvements|Improving CLI UX for setting up Supervisor IDPs|Exploring/Ongoing|
|
||||
|Telemetry|Adding some useful phone home metrics as well as some vanity metrics|Exploring/Ongoing|
|
||||
|Observability|Expose Pinniped metrics through Prometheus Integration|Exploring/Ongoing|
|
||||
|Device Code Flow|Add support for OAuth 2.0 Device Authorization Grant in the Pinniped CLI and Supervisor|Exploring/Ongoing|
|
||||
|
||||
|
||||
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
|
||||
100
SECURITY.md
100
SECURITY.md
@@ -1,12 +1,92 @@
|
||||
# Reporting a Vulnerability
|
||||
# Security Release Process
|
||||
|
||||
Pinniped development is sponsored by VMware, and the Pinniped team encourages users
|
||||
who become aware of a security vulnerability in Pinniped to report any potential
|
||||
vulnerabilities found to security@vmware.com. If possible, please include a description
|
||||
of the effects of the vulnerability, reproduction steps, and a description of in which
|
||||
version of Pinniped or its dependencies the vulnerability was discovered.
|
||||
The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
|
||||
Pinniped provides identity services for Kubernetes clusters. The community has adopted this security disclosure and response policy to ensure we responsibly handle critical issues.
|
||||
|
||||
The Pinniped team hopes that users encountering a new vulnerability will contact
|
||||
us privately as it is in the best interests of our users that the Pinniped team has
|
||||
an opportunity to investigate and confirm a suspected vulnerability before it becomes public knowledge.
|
||||
## Supported Versions
|
||||
|
||||
As of right now, only the latest version of Pinniped is supported.
|
||||
|
||||
## Reporting a Vulnerability - Private Disclosure Process
|
||||
|
||||
Security is of the highest importance and all security vulnerabilities or suspected security vulnerabilities should be reported to Pinniped privately, to minimize attacks against current users of Pinniped before they are fixed. Vulnerabilities will be investigated and patched on the next patch (or minor) release as soon as possible. This information could be kept entirely internal to the project.
|
||||
|
||||
If you know of a publicly disclosed security vulnerability for Pinniped, please **IMMEDIATELY** contact the VMware Security Team (security@vmware.com). The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
|
||||
|
||||
**IMPORTANT: Do not file public issues on GitHub for security vulnerabilities**
|
||||
|
||||
To report a vulnerability or a security-related issue, please contact the VMware email address with the details of the vulnerability. The email will be fielded by the VMware Security Team and then shared with the Pinniped maintainers who have committer and release permissions. Emails will be addressed within 3 business days, including a detailed plan to investigate the issue and any potential workarounds to perform in the meantime. Do not report non-security-impacting bugs through this channel. Use [GitHub issues](https://github.com/vmware-tanzu/pinniped/issues/new/choose) instead.
|
||||
|
||||
## Proposed Email Content
|
||||
|
||||
Provide a descriptive subject line and in the body of the email include the following information:
|
||||
|
||||
* Basic identity information, such as your name and your affiliation or company.
|
||||
* Detailed steps to reproduce the vulnerability (POC scripts, screenshots, and logs are all helpful to us).
|
||||
* Description of the effects of the vulnerability on Pinniped and the related hardware and software configurations, so that the VMware Security Team can reproduce it.
|
||||
* How the vulnerability affects Pinniped usage and an estimation of the attack surface, if there is one.
|
||||
* List other projects or dependencies that were used in conjunction with Pinniped to produce the vulnerability.
|
||||
|
||||
## When to report a vulnerability
|
||||
|
||||
* When you think Pinniped has a potential security vulnerability.
|
||||
* When you suspect a potential vulnerability but you are unsure that it impacts Pinniped.
|
||||
* When you know of or suspect a potential vulnerability on another project that is used by Pinniped.
|
||||
|
||||
## Patch, Release, and Disclosure
|
||||
|
||||
The VMware Security Team will respond to vulnerability reports as follows:
|
||||
|
||||
1. The Security Team will investigate the vulnerability and determine its effects and criticality.
|
||||
2. If the issue is not deemed to be a vulnerability, the Security Team will follow up with a detailed reason for rejection.
|
||||
3. The Security Team will initiate a conversation with the reporter within 3 business days.
|
||||
4. If a vulnerability is acknowledged and the timeline for a fix is determined, the Security Team will work on a plan to communicate with the appropriate community, including identifying mitigating steps that affected users can take to protect themselves until the fix is rolled out.
|
||||
5. The Security Team will also create a [CVSS](https://www.first.org/cvss/specification-document) using the [CVSS Calculator](https://www.first.org/cvss/calculator/3.0). The Security Team makes the final call on the calculated CVSS; it is better to move quickly than making the CVSS perfect. Issues may also be reported to [Mitre](https://cve.mitre.org/) using this [scoring calculator](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator). The CVE will initially be set to private.
|
||||
6. The Security Team will work on fixing the vulnerability and perform internal testing before preparing to roll out the fix.
|
||||
7. The Security Team will provide early disclosure of the vulnerability by emailing the [Pinniped Distributors](https://groups.google.com/g/project-pinniped-distributors) mailing list. Distributors can initially plan for the vulnerability patch ahead of the fix, and later can test the fix and provide feedback to the Pinniped team. See the section **Early Disclosure to Pinniped Distributors List** for details about how to join this mailing list.
|
||||
8. A public disclosure date is negotiated by the VMware SecurityTeam, the bug submitter, and the distributors list. We prefer to fully disclose the bug as soon as possible once a user mitigation or patch is available. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for distributor coordination. The timeframe for disclosure is from immediate (especially if it’s already publicly known) to a few weeks. For a critical vulnerability with a straightforward mitigation, we expect the report date for the public disclosure date to be on the order of 14 business days. The VMware Security Team holds the final say when setting a public disclosure date.
|
||||
9. Once the fix is confirmed, the Security Team will patch the vulnerability in the next patch or minor release, and backport a patch release into all earlier supported releases. Upon release of the patched version of Pinniped, we will follow the **Public Disclosure Process**.
|
||||
|
||||
## Public Disclosure Process
|
||||
|
||||
The Security Team publishes a [public advisory](https://github.com/vmware-tanzu/pinniped/security/advisories) to the Pinniped community via GitHub. In most cases, additional communication via Slack, Twitter, mailing lists, blog and other channels will assist in educating Pinniped users and rolling out the patched release to affected users.
|
||||
|
||||
The Security Team will also publish any mitigating steps users can take until the fix can be applied to their Pinniped instances. Pinniped distributors will handle creating and publishing their own security advisories.
|
||||
|
||||
## Mailing lists
|
||||
|
||||
* Use security@vmware.com to report security concerns to the VMware Security Team, who uses the list to privately discuss security issues and fixes prior to disclosure. The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
|
||||
* Join the [Pinniped Distributors](https://groups.google.com/g/project-pinniped-distributors) mailing list for early private information and vulnerability disclosure. Early disclosure may include mitigating steps and additional information on security patch releases. See below for information on how Pinniped distributors or vendors can apply to join this list.
|
||||
|
||||
## Early Disclosure to Pinniped Distributors List
|
||||
|
||||
The private list is intended to be used primarily to provide actionable information to multiple distributor projects at once. This list is not intended to inform individuals about security issues.
|
||||
|
||||
## Membership Criteria
|
||||
|
||||
To be eligible to join the [Pinniped Distributors](https://groups.google.com/g/project-pinniped-distributors) mailing list, you should:
|
||||
|
||||
1. Be an active distributor of Pinniped.
|
||||
2. Have a user base that is not limited to your own organization.
|
||||
3. Have a publicly verifiable track record up to the present day of fixing security issues.
|
||||
4. Not be a downstream or rebuild of another distributor.
|
||||
5. Be a participant and active contributor in the Pinniped community.
|
||||
6. Accept the Embargo Policy that is outlined below.
|
||||
7. Have someone who is already on the list vouch for the person requesting membership on behalf of your distribution.
|
||||
|
||||
**The terms and conditions of the Embargo Policy apply to all members of this mailing list. A request for membership represents your acceptance to the terms and conditions of the Embargo Policy.**
|
||||
|
||||
## Embargo Policy
|
||||
|
||||
The information that members receive on the Pinniped Distributors mailing list must not be made public, shared, or even hinted at anywhere beyond those who need to know within your specific team, unless you receive explicit approval to do so from the VMware Security Team. This remains true until the public disclosure date/time agreed upon by the list. Members of the list and others cannot use the information for any reason other than to get the issue fixed for your respective distribution's users.
|
||||
|
||||
Before you share any information from the list with members of your team who are required to fix the issue, these team members must agree to the same terms, and only be provided with information on a need-to-know basis.
|
||||
|
||||
In the unfortunate event that you share information beyond what is permitted by this policy, you must urgently inform the VMware Security Team (security@vmware.com) of exactly what information was leaked and to whom. If you continue to leak information and break the policy outlined here, you will be permanently removed from the list.
|
||||
|
||||
## Requesting to Join
|
||||
|
||||
Send new membership requests to https://groups.google.com/g/project-pinniped-distributors. In the body of your request please specify how you qualify for membership and fulfill each criterion listed in the Membership Criteria section above.
|
||||
|
||||
## Confidentiality, integrity and availability
|
||||
|
||||
We consider vulnerabilities leading to the compromise of data confidentiality, elevation of privilege, or integrity to be our highest priority concerns. Availability, in particular in areas relating to DoS and resource exhaustion, is also a serious security concern. The VMware Security Team takes all vulnerabilities, potential vulnerabilities, and suspected vulnerabilities seriously and will investigate them in an urgent and expeditious manner.
|
||||
|
||||
@@ -57,9 +57,11 @@ type JWTTokenClaims struct {
|
||||
// signature, existence of claims, etc.) and extract the username and groups from the token.
|
||||
//
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
|
||||
// +kubebuilder:subresource:status
|
||||
type JWTAuthenticator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
@@ -29,9 +29,11 @@ type WebhookAuthenticatorSpec struct {
|
||||
|
||||
// WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.spec.endpoint`
|
||||
// +kubebuilder:subresource:status
|
||||
type WebhookAuthenticator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
// 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"
|
||||
|
||||
// +kubebuilder:validation:Enum=KubeClusterSigningCertificate
|
||||
// +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy
|
||||
type StrategyType string
|
||||
|
||||
// +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy
|
||||
type FrontendType string
|
||||
|
||||
// +kubebuilder:validation:Enum=Success;Error
|
||||
type StrategyStatus string
|
||||
|
||||
// +kubebuilder:validation:Enum=FetchedKey;CouldNotFetchKey
|
||||
// +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.
|
||||
@@ -30,6 +42,7 @@ type CredentialIssuerStatus struct {
|
||||
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"`
|
||||
}
|
||||
@@ -63,17 +76,60 @@ 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"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// Describes the configuration status of a Pinniped credential issuer.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped
|
||||
// +kubebuilder:resource:categories=pinniped,scope=Cluster
|
||||
// +kubebuilder:subresource:status
|
||||
type CredentialIssuer struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Status of the credential issuer.
|
||||
// +optional
|
||||
Status CredentialIssuerStatus `json:"status"`
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
@@ -27,7 +27,6 @@ type TokenCredentialRequestStatus struct {
|
||||
}
|
||||
|
||||
// TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchange for a cluster-specific credential.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type TokenCredentialRequest struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
@@ -30,6 +30,8 @@ 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"`
|
||||
|
||||
@@ -109,6 +109,7 @@ type FederationDomainStatus struct {
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped
|
||||
// +kubebuilder:subresource:status
|
||||
type FederationDomain struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
@@ -54,12 +54,12 @@ const (
|
||||
)
|
||||
|
||||
type webhook struct {
|
||||
certProvider dynamiccert.Provider
|
||||
certProvider dynamiccert.Private
|
||||
secretInformer corev1informers.SecretInformer
|
||||
}
|
||||
|
||||
func newWebhook(
|
||||
certProvider dynamiccert.Provider,
|
||||
certProvider dynamiccert.Private,
|
||||
secretInformer corev1informers.SecretInformer,
|
||||
) *webhook {
|
||||
return &webhook{
|
||||
@@ -281,7 +281,7 @@ func respondWithAuthenticated(
|
||||
|
||||
func startControllers(
|
||||
ctx context.Context,
|
||||
dynamicCertProvider dynamiccert.Provider,
|
||||
dynamicCertProvider dynamiccert.Private,
|
||||
kubeClient kubernetes.Interface,
|
||||
kubeInformers kubeinformers.SharedInformerFactory,
|
||||
) {
|
||||
@@ -328,7 +328,7 @@ func startControllers(
|
||||
func startWebhook(
|
||||
ctx context.Context,
|
||||
l net.Listener,
|
||||
dynamicCertProvider dynamiccert.Provider,
|
||||
dynamicCertProvider dynamiccert.Private,
|
||||
secretInformer corev1informers.SecretInformer,
|
||||
) error {
|
||||
return newWebhook(dynamicCertProvider, secretInformer).start(ctx, l)
|
||||
@@ -355,7 +355,7 @@ func run() error {
|
||||
kubeinformers.WithNamespace(namespace),
|
||||
)
|
||||
|
||||
dynamicCertProvider := dynamiccert.New()
|
||||
dynamicCertProvider := dynamiccert.NewServingCert("local-user-authenticator-tls-serving-certificate")
|
||||
|
||||
startControllers(ctx, dynamicCertProvider, client.Kubernetes, kubeInformers)
|
||||
plog.Debug("controllers are ready")
|
||||
|
||||
@@ -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 main
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -99,7 +98,7 @@ func TestWebhook(t *testing.T) {
|
||||
},
|
||||
}))
|
||||
|
||||
secretInformer := createSecretInformer(t, kubeClient)
|
||||
secretInformer := createSecretInformer(ctx, t, kubeClient)
|
||||
|
||||
certProvider, caBundle, serverName := newCertProvider(t)
|
||||
w := newWebhook(certProvider, secretInformer)
|
||||
@@ -437,7 +436,7 @@ func TestWebhook(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func createSecretInformer(t *testing.T, kubeClient kubernetes.Interface) corev1informers.SecretInformer {
|
||||
func createSecretInformer(ctx context.Context, t *testing.T, kubeClient kubernetes.Interface) corev1informers.SecretInformer {
|
||||
t.Helper()
|
||||
|
||||
kubeInformers := kubeinformers.NewSharedInformerFactory(kubeClient, 0)
|
||||
@@ -448,9 +447,6 @@ func createSecretInformer(t *testing.T, kubeClient kubernetes.Interface) corev1i
|
||||
// informer factory before syncing it.
|
||||
secretInformer.Informer()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
||||
defer cancel()
|
||||
|
||||
kubeInformers.Start(ctx.Done())
|
||||
|
||||
informerTypesSynced := kubeInformers.WaitForCacheSync(ctx.Done())
|
||||
@@ -462,22 +458,23 @@ func createSecretInformer(t *testing.T, kubeClient kubernetes.Interface) corev1i
|
||||
// newClientProvider returns a dynamiccert.Provider configured
|
||||
// with valid serving cert, the CA bundle that can be used to verify the serving
|
||||
// cert, and the server name that can be used to verify the TLS peer.
|
||||
func newCertProvider(t *testing.T) (dynamiccert.Provider, []byte, string) {
|
||||
func newCertProvider(t *testing.T) (dynamiccert.Private, []byte, string) {
|
||||
t.Helper()
|
||||
|
||||
serverName := "local-user-authenticator"
|
||||
|
||||
ca, err := certauthority.New(pkix.Name{CommonName: serverName + " CA"}, time.Hour*24)
|
||||
ca, err := certauthority.New(serverName+" CA", time.Hour*24)
|
||||
require.NoError(t, err)
|
||||
|
||||
cert, err := ca.Issue(pkix.Name{CommonName: serverName}, []string{serverName}, nil, time.Hour*24)
|
||||
cert, err := ca.IssueServerCert([]string{serverName}, nil, time.Hour*24)
|
||||
require.NoError(t, err)
|
||||
|
||||
certPEM, keyPEM, err := certauthority.ToPEM(cert)
|
||||
require.NoError(t, err)
|
||||
|
||||
certProvider := dynamiccert.New()
|
||||
certProvider.Set(certPEM, keyPEM)
|
||||
certProvider := dynamiccert.NewServingCert(t.Name())
|
||||
err = certProvider.SetCertKeyContent(certPEM, keyPEM)
|
||||
require.NoError(t, err)
|
||||
|
||||
return certProvider, ca.Bundle(), serverName
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/klog/v2/klogr"
|
||||
|
||||
configv1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/config/v1alpha1"
|
||||
pinnipedclientset "go.pinniped.dev/generated/1.20/client/supervisor/clientset/versioned"
|
||||
pinnipedinformers "go.pinniped.dev/generated/1.20/client/supervisor/informers/externalversions"
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
||||
pinnipedclientset "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned"
|
||||
pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions"
|
||||
"go.pinniped.dev/internal/config/supervisor"
|
||||
"go.pinniped.dev/internal/controller/supervisorconfig"
|
||||
"go.pinniped.dev/internal/controller/supervisorconfig/generator"
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"go.pinniped.dev/internal/controllerlib"
|
||||
"go.pinniped.dev/internal/deploymentref"
|
||||
"go.pinniped.dev/internal/downward"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
"go.pinniped.dev/internal/kubeclient"
|
||||
"go.pinniped.dev/internal/oidc/jwks"
|
||||
"go.pinniped.dev/internal/oidc/provider"
|
||||
@@ -174,8 +175,8 @@ func startControllers(
|
||||
secretCache.SetTokenHMACKey(federationDomainIssuer, symmetricKey)
|
||||
},
|
||||
),
|
||||
func(fd *configv1alpha1.FederationDomain) *corev1.LocalObjectReference {
|
||||
return &fd.Status.Secrets.TokenSigningKey
|
||||
func(fd *configv1alpha1.FederationDomainStatus) *corev1.LocalObjectReference {
|
||||
return &fd.Secrets.TokenSigningKey
|
||||
},
|
||||
kubeClient,
|
||||
pinnipedClient,
|
||||
@@ -197,8 +198,8 @@ func startControllers(
|
||||
secretCache.SetStateEncoderHashKey(federationDomainIssuer, symmetricKey)
|
||||
},
|
||||
),
|
||||
func(fd *configv1alpha1.FederationDomain) *corev1.LocalObjectReference {
|
||||
return &fd.Status.Secrets.StateSigningKey
|
||||
func(fd *configv1alpha1.FederationDomainStatus) *corev1.LocalObjectReference {
|
||||
return &fd.Secrets.StateSigningKey
|
||||
},
|
||||
kubeClient,
|
||||
pinnipedClient,
|
||||
@@ -220,8 +221,8 @@ func startControllers(
|
||||
secretCache.SetStateEncoderBlockKey(federationDomainIssuer, symmetricKey)
|
||||
},
|
||||
),
|
||||
func(fd *configv1alpha1.FederationDomain) *corev1.LocalObjectReference {
|
||||
return &fd.Status.Secrets.StateEncryptionKey
|
||||
func(fd *configv1alpha1.FederationDomainStatus) *corev1.LocalObjectReference {
|
||||
return &fd.Secrets.StateEncryptionKey
|
||||
},
|
||||
kubeClient,
|
||||
pinnipedClient,
|
||||
@@ -258,13 +259,15 @@ func run(podInfo *downward.PodInfo, cfg *supervisor.Config) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// TODO remove code that relies on supervisorDeployment directly
|
||||
dref, supervisorDeployment, err := deploymentref.New(podInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create deployment ref: %w", err)
|
||||
}
|
||||
|
||||
client, err := kubeclient.New(dref)
|
||||
client, err := kubeclient.New(
|
||||
dref,
|
||||
kubeclient.WithMiddleware(groupsuffix.New(*cfg.APIGroupSuffix)),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create k8s client: %w", err)
|
||||
}
|
||||
|
||||
@@ -22,3 +22,9 @@ func mustMarkHidden(cmd *cobra.Command, flags ...string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustMarkDeprecated(cmd *cobra.Command, flag, usageMessage string) {
|
||||
if err := cmd.Flags().MarkDeprecated(flag, usageMessage); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"go.pinniped.dev/internal/here"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
//nolint: gochecknoinits
|
||||
func init() {
|
||||
rootCmd.AddCommand(legacyGetKubeconfigCommand(kubeconfigRealDeps()))
|
||||
rootCmd.AddCommand(legacyExchangeTokenCommand(staticLoginRealDeps()))
|
||||
}
|
||||
|
||||
func legacyGetKubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
||||
var (
|
||||
cmd = &cobra.Command{
|
||||
Hidden: true,
|
||||
Deprecated: "Please use `pinniped get kubeconfig` instead.",
|
||||
|
||||
Args: cobra.NoArgs, // do not accept positional arguments for this command
|
||||
Use: "get-kubeconfig",
|
||||
Short: "Print a kubeconfig for authenticating into a cluster via Pinniped",
|
||||
Long: here.Doc(`
|
||||
Print a kubeconfig for authenticating into a cluster via Pinniped.
|
||||
Requires admin-like access to the cluster using the current
|
||||
kubeconfig context in order to access Pinniped's metadata.
|
||||
The current kubeconfig is found similar to how kubectl finds it:
|
||||
using the value of the --kubeconfig option, or if that is not
|
||||
specified then from the value of the KUBECONFIG environment
|
||||
variable, or if that is not specified then it defaults to
|
||||
.kube/config in your home directory.
|
||||
Prints a kubeconfig which is suitable to access the cluster using
|
||||
Pinniped as the authentication mechanism. This kubeconfig output
|
||||
can be saved to a file and used with future kubectl commands, e.g.:
|
||||
pinniped get-kubeconfig --token $MY_TOKEN > $HOME/mycluster-kubeconfig
|
||||
kubectl --kubeconfig $HOME/mycluster-kubeconfig get pods
|
||||
`),
|
||||
}
|
||||
token string
|
||||
kubeconfig string
|
||||
contextOverride string
|
||||
namespace string
|
||||
authenticatorType string
|
||||
authenticatorName string
|
||||
)
|
||||
|
||||
cmd.Flags().StringVar(&token, "token", "", "Credential to include in the resulting kubeconfig output (Required)")
|
||||
cmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "Path to the kubeconfig file")
|
||||
cmd.Flags().StringVar(&contextOverride, "kubeconfig-context", "", "Kubeconfig context override")
|
||||
cmd.Flags().StringVar(&namespace, "pinniped-namespace", "pinniped-concierge", "Namespace in which Pinniped was installed")
|
||||
cmd.Flags().StringVar(&authenticatorType, "authenticator-type", "", "Authenticator type (e.g., 'webhook', 'jwt')")
|
||||
cmd.Flags().StringVar(&authenticatorName, "authenticator-name", "", "Authenticator name")
|
||||
mustMarkRequired(cmd, "token")
|
||||
plog.RemoveKlogGlobalFlags()
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
return runGetKubeconfig(cmd.OutOrStdout(), deps, getKubeconfigParams{
|
||||
kubeconfigPath: kubeconfig,
|
||||
kubeconfigContextOverride: contextOverride,
|
||||
staticToken: token,
|
||||
concierge: getKubeconfigConciergeParams{
|
||||
namespace: namespace,
|
||||
authenticatorName: authenticatorName,
|
||||
authenticatorType: authenticatorType,
|
||||
},
|
||||
})
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func legacyExchangeTokenCommand(deps staticLoginDeps) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Hidden: true,
|
||||
Deprecated: "Please use `pinniped login static` instead.",
|
||||
|
||||
Args: cobra.NoArgs, // do not accept positional arguments for this command
|
||||
Use: "exchange-credential",
|
||||
Short: "Exchange a credential for a cluster-specific access credential",
|
||||
Long: here.Doc(`
|
||||
Exchange a credential which proves your identity for a time-limited,
|
||||
cluster-specific access credential.
|
||||
Designed to be conveniently used as an credential plugin for kubectl.
|
||||
See the help message for 'pinniped get-kubeconfig' for more
|
||||
information about setting up a kubeconfig file using Pinniped.
|
||||
Requires all of the following environment variables, which are
|
||||
typically set in the kubeconfig:
|
||||
- PINNIPED_TOKEN: the token to send to Pinniped for exchange
|
||||
- PINNIPED_NAMESPACE: the namespace of the authenticator to authenticate
|
||||
against
|
||||
- PINNIPED_AUTHENTICATOR_TYPE: the type of authenticator to authenticate
|
||||
against (e.g., "webhook", "jwt")
|
||||
- PINNIPED_AUTHENTICATOR_NAME: the name of the authenticator to authenticate
|
||||
against
|
||||
- PINNIPED_CA_BUNDLE: the CA bundle to trust when calling
|
||||
Pinniped's HTTPS endpoint
|
||||
- PINNIPED_K8S_API_ENDPOINT: the URL for the Pinniped credential
|
||||
exchange API
|
||||
For more information about credential plugins in general, see
|
||||
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
|
||||
`),
|
||||
}
|
||||
plog.RemoveKlogGlobalFlags()
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
// Make a little helper to grab OS environment variables and keep a list that were missing.
|
||||
var missing []string
|
||||
getEnv := func(name string) string {
|
||||
value, ok := os.LookupEnv(name)
|
||||
if !ok {
|
||||
missing = append(missing, name)
|
||||
}
|
||||
return value
|
||||
}
|
||||
flags := staticLoginParams{
|
||||
staticToken: getEnv("PINNIPED_TOKEN"),
|
||||
conciergeEnabled: true,
|
||||
conciergeNamespace: getEnv("PINNIPED_NAMESPACE"),
|
||||
conciergeAuthenticatorType: getEnv("PINNIPED_AUTHENTICATOR_TYPE"),
|
||||
conciergeAuthenticatorName: getEnv("PINNIPED_AUTHENTICATOR_NAME"),
|
||||
conciergeEndpoint: getEnv("PINNIPED_K8S_API_ENDPOINT"),
|
||||
conciergeCABundle: base64.StdEncoding.EncodeToString([]byte(getEnv("PINNIPED_CA_BUNDLE"))),
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return fmt.Errorf("failed to get credential: required environment variable(s) not set: %v", missing)
|
||||
}
|
||||
return runStaticLogin(cmd.OutOrStdout(), deps, flags)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
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,50 +4,46 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"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/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"
|
||||
"go.pinniped.dev/internal/kubeclient"
|
||||
conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||
"go.pinniped.dev/internal/groupsuffix"
|
||||
)
|
||||
|
||||
type kubeconfigDeps struct {
|
||||
getPathToSelf func() (string, error)
|
||||
getClientset func(clientcmd.ClientConfig) (conciergeclientset.Interface, error)
|
||||
getClientset getConciergeClientsetFunc
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
func kubeconfigRealDeps() kubeconfigDeps {
|
||||
return kubeconfigDeps{
|
||||
getPathToSelf: os.Executable,
|
||||
getClientset: func(clientConfig clientcmd.ClientConfig) (conciergeclientset.Interface, error) {
|
||||
restConfig, err := clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := kubeclient.New(kubeclient.WithConfig(restConfig))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.PinnipedConcierge, nil
|
||||
},
|
||||
getClientset: getRealConciergeClientset,
|
||||
log: stdr.New(log.New(os.Stderr, "", 0)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,45 +60,64 @@ type getKubeconfigOIDCParams struct {
|
||||
skipBrowser bool
|
||||
sessionCachePath string
|
||||
debugSessionCache bool
|
||||
caBundlePaths []string
|
||||
caBundle caBundleFlag
|
||||
requestAudience string
|
||||
}
|
||||
|
||||
type getKubeconfigConciergeParams struct {
|
||||
disabled bool
|
||||
namespace string
|
||||
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 {
|
||||
var (
|
||||
cmd = cobra.Command{
|
||||
cmd = &cobra.Command{
|
||||
Args: cobra.NoArgs,
|
||||
Use: "kubeconfig",
|
||||
Short: "Generate a Pinniped-based kubeconfig for a cluster",
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags getKubeconfigParams
|
||||
flags getKubeconfigParams
|
||||
namespace string // unused now
|
||||
)
|
||||
|
||||
f := cmd.Flags()
|
||||
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(&flags.concierge.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", 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)")
|
||||
@@ -110,20 +125,46 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
|
||||
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.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.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")
|
||||
|
||||
mustMarkHidden(&cmd, "oidc-debug-session-cache")
|
||||
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) }
|
||||
return &cmd
|
||||
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{},
|
||||
@@ -137,38 +178,67 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
|
||||
}
|
||||
execConfig.ProvideClusterInfo = true
|
||||
|
||||
oidcCABundle, err := loadCABundlePaths(flags.oidc.caBundlePaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read --oidc-ca-bundle: %w", err)
|
||||
}
|
||||
|
||||
clientConfig := newClientConfig(flags.kubeconfigPath, flags.kubeconfigContextOverride)
|
||||
currentKubeConfig, err := clientConfig.RawConfig()
|
||||
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)
|
||||
}
|
||||
clientset, err := deps.getClientset(clientConfig)
|
||||
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.namespace,
|
||||
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
|
||||
}
|
||||
// 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),
|
||||
)
|
||||
|
||||
// Point kubectl at the concierge endpoint.
|
||||
cluster.Server = flags.concierge.endpoint
|
||||
cluster.CertificateAuthorityData = 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`.
|
||||
@@ -183,13 +253,18 @@ 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))
|
||||
|
||||
kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
|
||||
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeConfigAsYAML(out, kubeconfig)
|
||||
}
|
||||
|
||||
// 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 fmt.Errorf("could not autodiscover --oidc-issuer and none was provided")
|
||||
}
|
||||
execConfig.Args = append(execConfig.Args,
|
||||
"--issuer="+flags.oidc.issuer,
|
||||
@@ -202,8 +277,8 @@ func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigPar
|
||||
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)
|
||||
@@ -214,15 +289,135 @@ 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))
|
||||
kubeconfig := newExecKubeconfig(cluster, &execConfig, newKubeconfigNames)
|
||||
if err := validateKubeconfig(ctx, flags, kubeconfig, deps.log); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeConfigAsYAML(out, kubeconfig)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -230,71 +425,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/%s has invalid spec.tls.certificateAuthorityData: %w", auth.Namespace, auth.Name, err)
|
||||
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-namespace="+flags.concierge.namespace,
|
||||
"--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, namespace, 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()
|
||||
|
||||
@@ -302,9 +544,9 @@ func lookupAuthenticator(clientset conciergeclientset.Interface, namespace, auth
|
||||
if authName != "" && authType != "" {
|
||||
switch strings.ToLower(authType) {
|
||||
case "webhook":
|
||||
return clientset.AuthenticationV1alpha1().WebhookAuthenticators(namespace).Get(ctx, authName, metav1.GetOptions{})
|
||||
return clientset.AuthenticationV1alpha1().WebhookAuthenticators().Get(ctx, authName, metav1.GetOptions{})
|
||||
case "jwt":
|
||||
return clientset.AuthenticationV1alpha1().JWTAuthenticators(namespace).Get(ctx, authName, metav1.GetOptions{})
|
||||
return clientset.AuthenticationV1alpha1().JWTAuthenticators().Get(ctx, authName, metav1.GetOptions{})
|
||||
default:
|
||||
return nil, fmt.Errorf(`invalid authenticator type %q, supported values are "webhook" and "jwt"`, authType)
|
||||
}
|
||||
@@ -312,11 +554,11 @@ func lookupAuthenticator(clientset conciergeclientset.Interface, namespace, auth
|
||||
|
||||
// Otherwise list all the available authenticators and hope there's just a single one.
|
||||
|
||||
jwtAuths, err := clientset.AuthenticationV1alpha1().JWTAuthenticators(namespace).List(ctx, metav1.ListOptions{})
|
||||
jwtAuths, err := clientset.AuthenticationV1alpha1().JWTAuthenticators().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list JWTAuthenticator objects for autodiscovery: %w", err)
|
||||
}
|
||||
webhooks, err := clientset.AuthenticationV1alpha1().WebhookAuthenticators(namespace).List(ctx, metav1.ListOptions{})
|
||||
webhooks, err := clientset.AuthenticationV1alpha1().WebhookAuthenticators().List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list WebhookAuthenticator objects for autodiscovery: %w", err)
|
||||
}
|
||||
@@ -329,23 +571,20 @@ func lookupAuthenticator(clientset conciergeclientset.Interface, namespace, auth
|
||||
results = append(results, &webhooks.Items[i])
|
||||
}
|
||||
if len(results) == 0 {
|
||||
return nil, fmt.Errorf("no authenticators were found in namespace %q (try setting --concierge-namespace)", namespace)
|
||||
return nil, fmt.Errorf("no authenticators were found")
|
||||
}
|
||||
if len(results) > 1 {
|
||||
return nil, fmt.Errorf("multiple authenticators were found in namespace %q, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified", namespace)
|
||||
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 {
|
||||
@@ -358,14 +597,94 @@ 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())
|
||||
}
|
||||
}
|
||||
return currentKubeConfig.Clusters[context.Cluster], nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@ 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
|
||||
}
|
||||
|
||||
@@ -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 cmd
|
||||
@@ -16,12 +16,16 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"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"
|
||||
|
||||
"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 +38,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)
|
||||
},
|
||||
@@ -59,22 +65,24 @@ type oidcLoginFlags struct {
|
||||
debugSessionCache bool
|
||||
requestAudience string
|
||||
conciergeEnabled bool
|
||||
conciergeNamespace string
|
||||
conciergeAuthenticatorType string
|
||||
conciergeAuthenticatorName string
|
||||
conciergeEndpoint string
|
||||
conciergeCABundle string
|
||||
conciergeAPIGroupSuffix string
|
||||
credentialCachePath string
|
||||
}
|
||||
|
||||
func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
||||
var (
|
||||
cmd = cobra.Command{
|
||||
cmd = &cobra.Command{
|
||||
Args: cobra.NoArgs,
|
||||
Use: "oidc --issuer ISSUER",
|
||||
Short: "Login using an OpenID Connect provider",
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags oidcLoginFlags
|
||||
flags oidcLoginFlags
|
||||
conciergeNamespace string // unused now
|
||||
)
|
||||
cmd.Flags().StringVar(&flags.issuer, "issuer", "", "OpenID Connect issuer URL")
|
||||
cmd.Flags().StringVar(&flags.clientID, "client-id", "pinniped-cli", "OpenID Connect client ID")
|
||||
@@ -83,23 +91,34 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
|
||||
cmd.Flags().BoolVar(&flags.skipBrowser, "skip-browser", false, "Skip opening the browser (just print the URL)")
|
||||
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(&flags.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.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)")
|
||||
|
||||
mustMarkHidden(&cmd, "debug-session-cache")
|
||||
mustMarkRequired(&cmd, "issuer")
|
||||
mustMarkHidden(cmd, "debug-session-cache")
|
||||
mustMarkRequired(cmd, "issuer")
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error { return runOIDCLogin(cmd, deps, flags) }
|
||||
return &cmd
|
||||
|
||||
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
|
||||
mustMarkHidden(cmd, "concierge-namespace")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLoginFlags) error {
|
||||
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
|
||||
|
||||
@@ -115,6 +134,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),
|
||||
}
|
||||
@@ -131,13 +151,13 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
|
||||
if flags.conciergeEnabled {
|
||||
var err error
|
||||
concierge, err = conciergeclient.New(
|
||||
conciergeclient.WithNamespace(flags.conciergeNamespace),
|
||||
conciergeclient.WithEndpoint(flags.conciergeEndpoint),
|
||||
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
|
||||
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
|
||||
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid concierge parameters: %w", err)
|
||||
return fmt.Errorf("invalid Concierge parameters: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +176,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 {
|
||||
@@ -165,17 +202,28 @@ 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 makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, error) {
|
||||
pool := x509.NewCertPool()
|
||||
for _, p := range caBundlePaths {
|
||||
@@ -192,7 +240,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{
|
||||
@@ -200,7 +248,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 {
|
||||
@@ -219,6 +270,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.
|
||||
|
||||
@@ -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 cmd
|
||||
@@ -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",
|
||||
@@ -58,14 +61,15 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
|
||||
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)
|
||||
--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 Pinniped concierge endpoint
|
||||
--concierge-namespace string Namespace in which the concierge was installed (default "pinniped-concierge")
|
||||
--enable-concierge Exchange the OIDC ID 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 oidc
|
||||
--issuer string OpenID Connect issuer URL
|
||||
--listen-port uint16 TCP port for localhost listener (authorization code flow only)
|
||||
@@ -92,7 +96,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
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -119,6 +123,21 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
Error: could not read --ca-bundle-data: illegal base64 data at input byte 7
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "invalid API group suffix",
|
||||
args: []string{
|
||||
"--issuer", "test-issuer",
|
||||
"--enable-concierge",
|
||||
"--concierge-api-group-suffix", ".starts.with.dot",
|
||||
"--concierge-authenticator-type", "jwt",
|
||||
"--concierge-authenticator-name", "test-authenticator",
|
||||
"--concierge-endpoint", "https://127.0.0.1:1234/",
|
||||
},
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: invalid Concierge parameters: invalid API group suffix: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "login error",
|
||||
args: []string{
|
||||
@@ -126,7 +145,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--issuer", "test-issuer",
|
||||
},
|
||||
loginErr: fmt.Errorf("some login error"),
|
||||
wantOptionsCount: 3,
|
||||
wantOptionsCount: 4,
|
||||
wantError: true,
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not complete Pinniped login: some login error
|
||||
@@ -143,10 +162,10 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--concierge-endpoint", "https://127.0.0.1:1234/",
|
||||
},
|
||||
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
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -155,8 +174,14 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--client-id", "test-client-id",
|
||||
"--issuer", "test-issuer",
|
||||
},
|
||||
wantOptionsCount: 3,
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantOptionsCount: 4,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
|
||||
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\"",
|
||||
"\"level\"=0 \"msg\"=\"Pinniped login: caching cluster credential for future use.\"",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success with all options",
|
||||
@@ -170,23 +195,37 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--ca-bundle-data", base64.StdEncoding.EncodeToString(testCA.Bundle()),
|
||||
"--ca-bundle", testCABundlePath,
|
||||
"--enable-concierge",
|
||||
"--concierge-namespace", "test-namespace",
|
||||
"--concierge-authenticator-type", "webhook",
|
||||
"--concierge-authenticator-name", "test-authenticator",
|
||||
"--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",
|
||||
},
|
||||
wantOptionsCount: 7,
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantOptionsCount: 8,
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"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.\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
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)
|
||||
@@ -232,6 +271,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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 cmd
|
||||
@@ -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"
|
||||
)
|
||||
@@ -41,36 +45,50 @@ type staticLoginParams struct {
|
||||
staticToken string
|
||||
staticTokenEnvName string
|
||||
conciergeEnabled bool
|
||||
conciergeNamespace string
|
||||
conciergeAuthenticatorType string
|
||||
conciergeAuthenticatorName string
|
||||
conciergeEndpoint string
|
||||
conciergeCABundle string
|
||||
conciergeAPIGroupSuffix string
|
||||
credentialCachePath string
|
||||
}
|
||||
|
||||
func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
|
||||
var (
|
||||
cmd = cobra.Command{
|
||||
cmd = &cobra.Command{
|
||||
Args: cobra.NoArgs,
|
||||
Use: "static [--token TOKEN] [--token-env TOKEN_NAME]",
|
||||
Short: "Login using a static token",
|
||||
SilenceUsage: true,
|
||||
}
|
||||
flags staticLoginParams
|
||||
flags staticLoginParams
|
||||
conciergeNamespace string // unused now
|
||||
)
|
||||
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(&flags.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.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) }
|
||||
return &cmd
|
||||
|
||||
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
|
||||
mustMarkHidden(cmd, "concierge-namespace")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
@@ -79,13 +97,13 @@ func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams
|
||||
if flags.conciergeEnabled {
|
||||
var err error
|
||||
concierge, err = conciergeclient.New(
|
||||
conciergeclient.WithNamespace(flags.conciergeNamespace),
|
||||
conciergeclient.WithEndpoint(flags.conciergeEndpoint),
|
||||
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
|
||||
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
|
||||
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid concierge parameters: %w", err)
|
||||
return fmt.Errorf("invalid Concierge parameters: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,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)
|
||||
}
|
||||
|
||||
@@ -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 cmd
|
||||
@@ -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",
|
||||
@@ -51,12 +57,13 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
static [--token TOKEN] [--token-env TOKEN_NAME] [flags]
|
||||
|
||||
Flags:
|
||||
--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
|
||||
--concierge-namespace string Namespace in which the concierge was installed (default "pinniped-concierge")
|
||||
--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
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -124,9 +131,26 @@ 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",
|
||||
args: []string{
|
||||
"--token", "test-token",
|
||||
"--enable-concierge",
|
||||
"--concierge-api-group-suffix", ".starts.with.dot",
|
||||
"--concierge-authenticator-type", "jwt",
|
||||
"--concierge-authenticator-name", "test-authenticator",
|
||||
"--concierge-endpoint", "https://127.0.0.1:1234/",
|
||||
},
|
||||
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])?)*')
|
||||
`),
|
||||
},
|
||||
{
|
||||
@@ -134,12 +158,15 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
args: []string{
|
||||
"--token", "test-token",
|
||||
},
|
||||
env: map[string]string{"PINNIPED_DEBUG": "true"},
|
||||
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"test-token"}}` + "\n",
|
||||
},
|
||||
}
|
||||
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]
|
||||
@@ -175,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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 cmd
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
@@ -17,6 +19,12 @@ var rootCmd = &cobra.Command{
|
||||
SilenceUsage: true, // do not print usage message when commands fail
|
||||
}
|
||||
|
||||
//nolint: gochecknoinits
|
||||
func init() {
|
||||
// We don't want klog flags showing up in our CLI.
|
||||
plog.RemoveKlogGlobalFlags()
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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://github.com/vmware-tanzu/pinniped/releases/download/$(curl https://api.github.com/repos/vmware-tanzu/pinniped/releases/latest -s | jq .name -r)/install-pinniped-concierge.yaml
|
||||
```
|
||||
|
||||
## Installing an Older 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.2.0 with your preferred version in the URL below
|
||||
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/download/v0.2.0/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/).
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
listKind: JWTAuthenticatorList
|
||||
plural: jwtauthenticators
|
||||
singular: jwtauthenticator
|
||||
scope: Namespaced
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.issuer
|
||||
@@ -161,7 +161,8 @@ spec:
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources: {}
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
listKind: WebhookAuthenticatorList
|
||||
plural: webhookauthenticators
|
||||
singular: webhookauthenticator
|
||||
scope: Namespaced
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.endpoint
|
||||
@@ -137,7 +137,8 @@ spec:
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources: {}
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
@@ -16,7 +16,7 @@ spec:
|
||||
listKind: CredentialIssuerList
|
||||
plural: credentialissuers
|
||||
singular: credentialissuer
|
||||
scope: Namespaced
|
||||
scope: Cluster
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
@@ -40,7 +40,8 @@ spec:
|
||||
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.
|
||||
@@ -62,6 +63,59 @@ spec:
|
||||
description: 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 +127,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 +145,7 @@ spec:
|
||||
description: Type of integration attempted.
|
||||
enum:
|
||||
- KubeClusterSigningCertificate
|
||||
- ImpersonationProxy
|
||||
type: string
|
||||
required:
|
||||
- lastUpdateTime
|
||||
@@ -98,11 +158,11 @@ spec:
|
||||
required:
|
||||
- strategies
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("@ytt:json", "json")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel")
|
||||
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel", "pinnipedDevAPIGroupWithPrefix")
|
||||
|
||||
#@ if not data.values.into_namespace:
|
||||
---
|
||||
@@ -22,6 +22,13 @@ metadata:
|
||||
labels: #@ labels()
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("kube-cert-agent")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("config")
|
||||
@@ -37,10 +44,17 @@ data:
|
||||
servingCertificate:
|
||||
durationSeconds: (@= str(data.values.api_serving_certificate_duration_seconds) @)
|
||||
renewBeforeSeconds: (@= str(data.values.api_serving_certificate_renew_before_seconds) @)
|
||||
apiGroupSuffix: (@= data.values.api_group_suffix @)
|
||||
names:
|
||||
servingCertificateSecret: (@= defaultResourceNameWithSuffix("api-tls-serving-certificate") @)
|
||||
credentialIssuer: (@= defaultResourceNameWithSuffix("config") @)
|
||||
apiService: (@= defaultResourceNameWithSuffix("api") @)
|
||||
impersonationConfigMap: (@= defaultResourceNameWithSuffix("impersonation-proxy-config") @)
|
||||
impersonationLoadBalancerService: (@= defaultResourceNameWithSuffix("impersonation-proxy-load-balancer") @)
|
||||
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-") @)
|
||||
@@ -90,8 +104,8 @@ spec:
|
||||
scheduler.alpha.kubernetes.io/critical-pod: ""
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsUser: #@ data.values.run_as_user
|
||||
runAsGroup: #@ data.values.run_as_group
|
||||
serviceAccountName: #@ defaultResourceName()
|
||||
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
|
||||
imagePullSecrets:
|
||||
@@ -188,16 +202,46 @@ spec:
|
||||
port: 443
|
||||
targetPort: 8443
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: #@ defaultResourceNameWithSuffix("proxy")
|
||||
namespace: #@ namespace()
|
||||
labels: #@ labels()
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector: #@ defaultLabel()
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
targetPort: 8444
|
||||
---
|
||||
apiVersion: apiregistration.k8s.io/v1
|
||||
kind: APIService
|
||||
metadata:
|
||||
name: v1alpha1.login.concierge.pinniped.dev
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("v1alpha1.login.concierge")
|
||||
labels: #@ labels()
|
||||
spec:
|
||||
version: v1alpha1
|
||||
group: login.concierge.pinniped.dev
|
||||
groupPriorityMinimum: 2500
|
||||
versionPriority: 10
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("login.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: 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")
|
||||
|
||||
@@ -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
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
@@ -12,6 +12,10 @@
|
||||
#@ return data.values.app_name + "-" + suffix
|
||||
#@ end
|
||||
|
||||
#@ def pinnipedDevAPIGroupWithPrefix(prefix):
|
||||
#@ return prefix + "." + data.values.api_group_suffix
|
||||
#@ end
|
||||
|
||||
#@ def namespace():
|
||||
#@ if data.values.into_namespace:
|
||||
#@ return data.values.into_namespace
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
|
||||
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "pinnipedDevAPIGroupWithPrefix")
|
||||
|
||||
#! Give permission to various cluster-scoped objects
|
||||
---
|
||||
@@ -17,17 +17,38 @@ rules:
|
||||
verbs: [ get, list, watch ]
|
||||
- apiGroups: [ apiregistration.k8s.io ]
|
||||
resources: [ apiservices ]
|
||||
verbs: [ create, get, list, patch, update, watch ]
|
||||
verbs: [ get, list, patch, update, watch ]
|
||||
- apiGroups: [ admissionregistration.k8s.io ]
|
||||
resources: [ validatingwebhookconfigurations, mutatingwebhookconfigurations ]
|
||||
verbs: [ get, list, watch ]
|
||||
- apiGroups: [ policy ]
|
||||
resources: [ podsecuritypolicies ]
|
||||
verbs: [ use ]
|
||||
- apiGroups: [ flowcontrol.apiserver.k8s.io ]
|
||||
resources: [ flowschemas, prioritylevelconfigurations ]
|
||||
verbs: [ get, list, watch ]
|
||||
- apiGroups: [ security.openshift.io ]
|
||||
resources: [ securitycontextconstraints ]
|
||||
verbs: [ use ]
|
||||
resourceNames: [ nonroot ]
|
||||
- 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" ]
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ nodes ]
|
||||
verbs: [ list ]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("config.concierge")
|
||||
resources: [ credentialissuers ]
|
||||
verbs: [ get, list, watch, create ]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("config.concierge")
|
||||
resources: [ credentialissuers/status ]
|
||||
verbs: [ get, patch, update ]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
|
||||
resources: [ jwtauthenticators, webhookauthenticators ]
|
||||
verbs: [ get, list, watch ]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -43,6 +64,34 @@ roleRef:
|
||||
name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
|
||||
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
|
||||
@@ -54,24 +103,33 @@ 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: [ config.concierge.pinniped.dev, authentication.concierge.pinniped.dev ]
|
||||
resources: [ "*" ]
|
||||
verbs: [ create, get, list, update, watch ]
|
||||
- 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 ]
|
||||
#! 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 ]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -121,17 +179,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: [ login.concierge.pinniped.dev ]
|
||||
- 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
|
||||
@@ -142,7 +205,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
|
||||
|
||||
@@ -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
|
||||
|
||||
#@data/values
|
||||
@@ -54,3 +54,12 @@ api_serving_certificate_renew_before_seconds: 2160000
|
||||
#! Specify the verbosity of logging: info ("nice to know" information), debug (developer
|
||||
#! information), trace (timing information), all (kitchen sink).
|
||||
log_level: #! By default, when this value is left unset, only warnings and errors are printed. There is no way to suppress warning and error logs.
|
||||
|
||||
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
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
#! 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:overlay", "overlay")
|
||||
#@ load("helpers.lib.yaml", "labels")
|
||||
#@ load("helpers.lib.yaml", "labels", "pinnipedDevAPIGroupWithPrefix")
|
||||
#@ load("@ytt:data", "data")
|
||||
|
||||
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"credentialissuers.config.concierge.pinniped.dev"}}), expects=1
|
||||
---
|
||||
metadata:
|
||||
#@overlay/match missing_ok=True
|
||||
labels: #@ labels()
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("credentialissuers.config.concierge")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("config.concierge")
|
||||
|
||||
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"webhookauthenticators.authentication.concierge.pinniped.dev"}}), expects=1
|
||||
---
|
||||
metadata:
|
||||
#@overlay/match missing_ok=True
|
||||
labels: #@ labels()
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("webhookauthenticators.authentication.concierge")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
|
||||
|
||||
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"jwtauthenticators.authentication.concierge.pinniped.dev"}}), expects=1
|
||||
---
|
||||
metadata:
|
||||
#@overlay/match missing_ok=True
|
||||
labels: #@ labels()
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("jwtauthenticators.authentication.concierge")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
|
||||
|
||||
@@ -15,17 +15,17 @@ User accounts can be created and edited dynamically using `kubectl` commands (se
|
||||
## Installing the Latest Version with Default Options
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/latest/download/install-local-user-authenticator.yaml
|
||||
kubectl apply -f https://get.pinniped.dev/latest/install-local-user-authenticator.yaml
|
||||
```
|
||||
|
||||
## Installing an Older Version with Default Options
|
||||
## 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.2.0 with your preferred version in the URL below
|
||||
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/download/v0.2.0/install-local-user-authenticator.yaml
|
||||
# Replace v0.4.1 with your preferred version in the URL below
|
||||
kubectl apply -f https://get.pinniped.dev/v0.4.1/install-local-user-authenticator.yaml
|
||||
```
|
||||
|
||||
## Installing with Custom Options
|
||||
|
||||
@@ -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
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
@@ -48,8 +48,8 @@ spec:
|
||||
app: local-user-authenticator
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsUser: #@ data.values.run_as_user
|
||||
runAsGroup: #@ data.values.run_as_group
|
||||
serviceAccountName: local-user-authenticator
|
||||
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
|
||||
imagePullSecrets:
|
||||
|
||||
@@ -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
|
||||
|
||||
#@data/values
|
||||
@@ -14,3 +14,6 @@ image_tag: latest
|
||||
#! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]'
|
||||
#! Optional.
|
||||
image_pull_dockerconfigjson: #! e.g. {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}
|
||||
|
||||
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://github.com/vmware-tanzu/pinniped/releases/latest/download/install-pinniped-supervisor.yaml
|
||||
```
|
||||
|
||||
## Installing an Older 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.3.0 with your preferred version in the URL below
|
||||
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/download/v0.3.0/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/).
|
||||
|
||||
@@ -150,6 +150,8 @@ spec:
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
@@ -30,6 +30,7 @@ metadata:
|
||||
data:
|
||||
#@yaml/text-templated-strings
|
||||
pinniped.yaml: |
|
||||
apiGroupSuffix: (@= data.values.api_group_suffix @)
|
||||
names:
|
||||
defaultTLSCertificateSecret: (@= defaultResourceNameWithSuffix("default-tls-certificate") @)
|
||||
labels: (@= json.encode(labels()).rstrip() @)
|
||||
@@ -64,8 +65,8 @@ spec:
|
||||
labels: #@ defaultLabel()
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
runAsUser: #@ data.values.run_as_user
|
||||
runAsGroup: #@ data.values.run_as_group
|
||||
serviceAccountName: #@ defaultResourceName()
|
||||
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
|
||||
imagePullSecrets:
|
||||
@@ -101,15 +102,6 @@ spec:
|
||||
protocol: TCP
|
||||
- containerPort: 8443
|
||||
protocol: TCP
|
||||
env:
|
||||
#@ if data.values.https_proxy:
|
||||
- name: HTTPS_PROXY
|
||||
value: #@ data.values.https_proxy
|
||||
#@ end
|
||||
#@ if data.values.no_proxy:
|
||||
- name: NO_PROXY
|
||||
value: #@ data.values.no_proxy
|
||||
#@ end
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
|
||||
@@ -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
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
@@ -12,6 +12,10 @@
|
||||
#@ return data.values.app_name + "-" + suffix
|
||||
#@ end
|
||||
|
||||
#@ def pinnipedDevAPIGroupWithPrefix(prefix):
|
||||
#@ return prefix + "." + data.values.api_group_suffix
|
||||
#@ end
|
||||
|
||||
#@ def namespace():
|
||||
#@ if data.values.into_namespace:
|
||||
#@ return data.values.into_namespace
|
||||
|
||||
@@ -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", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
|
||||
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "pinnipedDevAPIGroupWithPrefix")
|
||||
|
||||
#! Give permission to various objects within the app's own namespace
|
||||
---
|
||||
@@ -16,13 +16,20 @@ rules:
|
||||
- apiGroups: [""]
|
||||
resources: [secrets]
|
||||
verbs: [create, get, list, patch, update, watch, delete]
|
||||
- apiGroups: [config.supervisor.pinniped.dev]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("config.supervisor")
|
||||
resources: [federationdomains]
|
||||
verbs: [update, get, list, watch]
|
||||
- apiGroups: [idp.supervisor.pinniped.dev]
|
||||
verbs: [get, list, watch]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("config.supervisor")
|
||||
resources: [federationdomains/status]
|
||||
verbs: [get, patch, update]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [oidcidentityproviders]
|
||||
verbs: [get, list, watch]
|
||||
- apiGroups: [idp.supervisor.pinniped.dev]
|
||||
- apiGroups:
|
||||
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
resources: [oidcidentityproviders/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
|
||||
|
||||
@@ -57,10 +57,11 @@ 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.
|
||||
|
||||
#! 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: #! e.g. 127.0.0.1
|
||||
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
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
#! 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:overlay", "overlay")
|
||||
#@ load("helpers.lib.yaml", "labels")
|
||||
#@ load("helpers.lib.yaml", "labels", "pinnipedDevAPIGroupWithPrefix")
|
||||
#@ load("@ytt:data", "data")
|
||||
|
||||
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"federationdomains.config.supervisor.pinniped.dev"}}), expects=1
|
||||
---
|
||||
metadata:
|
||||
#@overlay/match missing_ok=True
|
||||
labels: #@ labels()
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("federationdomains.config.supervisor")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("config.supervisor")
|
||||
|
||||
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"oidcidentityproviders.idp.supervisor.pinniped.dev"}}), expects=1
|
||||
---
|
||||
metadata:
|
||||
#@overlay/match missing_ok=True
|
||||
labels: #@ labels()
|
||||
name: #@ pinnipedDevAPIGroupWithPrefix("oidcidentityproviders.idp.supervisor")
|
||||
spec:
|
||||
group: #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
|
||||
|
||||
257
generated/1.17/README.adoc
generated
257
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$$]
|
||||
|
||||
@@ -234,6 +236,25 @@ Describes the configuration status of a Pinniped credential issuer.
|
||||
|===
|
||||
|
||||
|
||||
[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".
|
||||
|===
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerkubeconfiginfo"]
|
||||
==== CredentialIssuerKubeConfigInfo
|
||||
|
||||
@@ -268,7 +289,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 +311,43 @@ 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-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 +462,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
|
||||
|
||||
|
||||
@@ -57,9 +57,11 @@ type JWTTokenClaims struct {
|
||||
// signature, existence of claims, etc.) and extract the username and groups from the token.
|
||||
//
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
|
||||
// +kubebuilder:subresource:status
|
||||
type JWTAuthenticator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
@@ -29,9 +29,11 @@ type WebhookAuthenticatorSpec struct {
|
||||
|
||||
// WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators
|
||||
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.spec.endpoint`
|
||||
// +kubebuilder:subresource:status
|
||||
type WebhookAuthenticator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
@@ -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,27 +1,39 @@
|
||||
// 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"
|
||||
|
||||
// +kubebuilder:validation:Enum=KubeClusterSigningCertificate
|
||||
// +kubebuilder:validation:Enum=KubeClusterSigningCertificate;ImpersonationProxy
|
||||
type StrategyType string
|
||||
|
||||
// +kubebuilder:validation:Enum=TokenCredentialRequestAPI;ImpersonationProxy
|
||||
type FrontendType string
|
||||
|
||||
// +kubebuilder:validation:Enum=Success;Error
|
||||
type StrategyStatus string
|
||||
|
||||
// +kubebuilder:validation:Enum=FetchedKey;CouldNotFetchKey
|
||||
// +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.
|
||||
@@ -30,6 +42,7 @@ type CredentialIssuerStatus struct {
|
||||
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"`
|
||||
}
|
||||
@@ -63,17 +76,60 @@ 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"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// Describes the configuration status of a Pinniped credential issuer.
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped
|
||||
// +kubebuilder:resource:categories=pinniped,scope=Cluster
|
||||
// +kubebuilder:subresource:status
|
||||
type CredentialIssuer struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Status of the credential issuer.
|
||||
// +optional
|
||||
Status CredentialIssuerStatus `json:"status"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -38,6 +38,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
|
||||
@@ -119,6 +145,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 +162,35 @@ 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 *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
|
||||
}
|
||||
@@ -27,7 +27,6 @@ type TokenCredentialRequestStatus struct {
|
||||
}
|
||||
|
||||
// TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchange for a cluster-specific credential.
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type TokenCredentialRequest struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
@@ -30,6 +30,8 @@ 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.
|
||||
|
||||
@@ -109,6 +109,7 @@ type FederationDomainStatus struct {
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:resource:categories=pinniped
|
||||
// +kubebuilder:subresource:status
|
||||
type FederationDomain struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
@@ -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
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
authenticationv1alpha1 "go.pinniped.dev/generated/1.17/client/concierge/clientset/versioned/typed/authentication/v1alpha1"
|
||||
configv1alpha1 "go.pinniped.dev/generated/1.17/client/concierge/clientset/versioned/typed/config/v1alpha1"
|
||||
identityv1alpha1 "go.pinniped.dev/generated/1.17/client/concierge/clientset/versioned/typed/identity/v1alpha1"
|
||||
loginv1alpha1 "go.pinniped.dev/generated/1.17/client/concierge/clientset/versioned/typed/login/v1alpha1"
|
||||
discovery "k8s.io/client-go/discovery"
|
||||
rest "k8s.io/client-go/rest"
|
||||
@@ -20,6 +21,7 @@ type Interface interface {
|
||||
Discovery() discovery.DiscoveryInterface
|
||||
AuthenticationV1alpha1() authenticationv1alpha1.AuthenticationV1alpha1Interface
|
||||
ConfigV1alpha1() configv1alpha1.ConfigV1alpha1Interface
|
||||
IdentityV1alpha1() identityv1alpha1.IdentityV1alpha1Interface
|
||||
LoginV1alpha1() loginv1alpha1.LoginV1alpha1Interface
|
||||
}
|
||||
|
||||
@@ -29,6 +31,7 @@ type Clientset struct {
|
||||
*discovery.DiscoveryClient
|
||||
authenticationV1alpha1 *authenticationv1alpha1.AuthenticationV1alpha1Client
|
||||
configV1alpha1 *configv1alpha1.ConfigV1alpha1Client
|
||||
identityV1alpha1 *identityv1alpha1.IdentityV1alpha1Client
|
||||
loginV1alpha1 *loginv1alpha1.LoginV1alpha1Client
|
||||
}
|
||||
|
||||
@@ -42,6 +45,11 @@ func (c *Clientset) ConfigV1alpha1() configv1alpha1.ConfigV1alpha1Interface {
|
||||
return c.configV1alpha1
|
||||
}
|
||||
|
||||
// IdentityV1alpha1 retrieves the IdentityV1alpha1Client
|
||||
func (c *Clientset) IdentityV1alpha1() identityv1alpha1.IdentityV1alpha1Interface {
|
||||
return c.identityV1alpha1
|
||||
}
|
||||
|
||||
// LoginV1alpha1 retrieves the LoginV1alpha1Client
|
||||
func (c *Clientset) LoginV1alpha1() loginv1alpha1.LoginV1alpha1Interface {
|
||||
return c.loginV1alpha1
|
||||
@@ -76,6 +84,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.identityV1alpha1, err = identityv1alpha1.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.loginV1alpha1, err = loginv1alpha1.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -94,6 +106,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||
var cs Clientset
|
||||
cs.authenticationV1alpha1 = authenticationv1alpha1.NewForConfigOrDie(c)
|
||||
cs.configV1alpha1 = configv1alpha1.NewForConfigOrDie(c)
|
||||
cs.identityV1alpha1 = identityv1alpha1.NewForConfigOrDie(c)
|
||||
cs.loginV1alpha1 = loginv1alpha1.NewForConfigOrDie(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
|
||||
@@ -105,6 +118,7 @@ func New(c rest.Interface) *Clientset {
|
||||
var cs Clientset
|
||||
cs.authenticationV1alpha1 = authenticationv1alpha1.New(c)
|
||||
cs.configV1alpha1 = configv1alpha1.New(c)
|
||||
cs.identityV1alpha1 = identityv1alpha1.New(c)
|
||||
cs.loginV1alpha1 = loginv1alpha1.New(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user