├── .gitignore
├── CONTRIBUTING.md
├── Gopkg.lock
├── Gopkg.toml
├── LICENSE
├── README.md
├── main.go
└── templates.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.dll
4 | *.so
5 | *.dylib
6 |
7 | # Test binary, build with `go test -c`
8 | *.test
9 |
10 | # Output of the go coverage tool, specifically when used with LiteIDE
11 | *.out
12 |
13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
14 | .glide/
15 |
16 | # Do not check in vendored dependencies
17 | vendor/
18 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | To develop on this project, please fork the repo and clone into your `$GOPATH`.
3 |
4 | Dependencies are **not** checked in so please download those separately.
5 | Download the dependencies using [`dep`](https://github.com/golang/dep).
6 |
7 | ```bash
8 | cd $GOPATH/src/github.com # Create this directory if it doesn't exist
9 | git clone git@github.com:/k8s-auth-example pusher/k8s-auth-example
10 | dep ensure # Installs dependencies to vendor folder.
11 | ```
12 |
13 | ## Pull Requests and Issues
14 | We track bugs and issues using Github .
15 |
16 | If you find a bug, please open an Issue.
17 |
18 | If you want to fix a bug, please fork, fix the bug and open a PR back to this repo.
19 | Please mention the open bug issue number within your PR if applicable.
20 |
--------------------------------------------------------------------------------
/Gopkg.lock:
--------------------------------------------------------------------------------
1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
2 |
3 |
4 | [[projects]]
5 | name = "github.com/PuerkitoBio/purell"
6 | packages = ["."]
7 | revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
8 | version = "v1.1.0"
9 |
10 | [[projects]]
11 | branch = "master"
12 | name = "github.com/PuerkitoBio/urlesc"
13 | packages = ["."]
14 | revision = "de5bf2ad457846296e2031421a34e2568e304e35"
15 |
16 | [[projects]]
17 | branch = "master"
18 | name = "github.com/coreos/go-oidc"
19 | packages = ["."]
20 | revision = "a4973d9a4225417aecf5d450a9522f00c1f7130f"
21 |
22 | [[projects]]
23 | name = "github.com/emicklei/go-restful"
24 | packages = [".","log"]
25 | revision = "5741799b275a3c4a5a9623a993576d7545cf7b5c"
26 | version = "v2.4.0"
27 |
28 | [[projects]]
29 | name = "github.com/fatih/structs"
30 | packages = ["."]
31 | revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
32 | version = "v1.0"
33 |
34 | [[projects]]
35 | name = "github.com/ghodss/yaml"
36 | packages = ["."]
37 | revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
38 | version = "v1.0.0"
39 |
40 | [[projects]]
41 | branch = "master"
42 | name = "github.com/go-openapi/jsonpointer"
43 | packages = ["."]
44 | revision = "779f45308c19820f1a69e9a4cd965f496e0da10f"
45 |
46 | [[projects]]
47 | branch = "master"
48 | name = "github.com/go-openapi/jsonreference"
49 | packages = ["."]
50 | revision = "36d33bfe519efae5632669801b180bf1a245da3b"
51 |
52 | [[projects]]
53 | branch = "master"
54 | name = "github.com/go-openapi/spec"
55 | packages = ["."]
56 | revision = "7abd5745472fff5eb3685386d5fb8bf38683154d"
57 |
58 | [[projects]]
59 | branch = "master"
60 | name = "github.com/go-openapi/swag"
61 | packages = ["."]
62 | revision = "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
63 |
64 | [[projects]]
65 | name = "github.com/gogo/protobuf"
66 | packages = ["proto","sortkeys"]
67 | revision = "100ba4e885062801d56799d78530b73b178a78f3"
68 | version = "v0.4"
69 |
70 | [[projects]]
71 | branch = "master"
72 | name = "github.com/golang/glog"
73 | packages = ["."]
74 | revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
75 |
76 | [[projects]]
77 | branch = "master"
78 | name = "github.com/golang/protobuf"
79 | packages = ["proto"]
80 | revision = "130e6b02ab059e7b717a096f397c5b60111cae74"
81 |
82 | [[projects]]
83 | branch = "master"
84 | name = "github.com/golang/snappy"
85 | packages = ["."]
86 | revision = "553a641470496b2327abcac10b36396bd98e45c9"
87 |
88 | [[projects]]
89 | branch = "master"
90 | name = "github.com/google/btree"
91 | packages = ["."]
92 | revision = "316fb6d3f031ae8f4d457c6c5186b9e3ded70435"
93 |
94 | [[projects]]
95 | branch = "master"
96 | name = "github.com/google/gofuzz"
97 | packages = ["."]
98 | revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
99 |
100 | [[projects]]
101 | branch = "master"
102 | name = "github.com/gregjones/httpcache"
103 | packages = [".","diskcache"]
104 | revision = "c1f8028e62adb3d518b823a2f8e6a95c38bdd3aa"
105 |
106 | [[projects]]
107 | branch = "master"
108 | name = "github.com/hashicorp/errwrap"
109 | packages = ["."]
110 | revision = "7554cd9344cec97297fa6649b055a8c98c2a1e55"
111 |
112 | [[projects]]
113 | branch = "master"
114 | name = "github.com/hashicorp/go-cleanhttp"
115 | packages = ["."]
116 | revision = "3573b8b52aa7b37b9358d966a898feb387f62437"
117 |
118 | [[projects]]
119 | branch = "master"
120 | name = "github.com/hashicorp/go-multierror"
121 | packages = ["."]
122 | revision = "83588e72410abfbe4df460eeb6f30841ae47d4c4"
123 |
124 | [[projects]]
125 | branch = "master"
126 | name = "github.com/hashicorp/go-rootcerts"
127 | packages = ["."]
128 | revision = "6bb64b370b90e7ef1fa532be9e591a81c3493e00"
129 |
130 | [[projects]]
131 | branch = "master"
132 | name = "github.com/hashicorp/hcl"
133 | packages = [".","hcl/ast","hcl/parser","hcl/scanner","hcl/strconv","hcl/token","json/parser","json/scanner","json/token"]
134 | revision = "68e816d1c783414e79bc65b3994d9ab6b0a722ab"
135 |
136 | [[projects]]
137 | name = "github.com/hashicorp/vault"
138 | packages = ["api","helper/compressutil","helper/jsonutil","helper/parseutil"]
139 | revision = "6b29fb2b7f70ed538ee2b3c057335d706b6d4e36"
140 | version = "v0.8.3"
141 |
142 | [[projects]]
143 | branch = "master"
144 | name = "github.com/howeyc/gopass"
145 | packages = ["."]
146 | revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8"
147 |
148 | [[projects]]
149 | name = "github.com/imdario/mergo"
150 | packages = ["."]
151 | revision = "3e95a51e0639b4cf372f2ccf74c86749d747fbdc"
152 | version = "0.2.2"
153 |
154 | [[projects]]
155 | name = "github.com/inconshreveable/mousetrap"
156 | packages = ["."]
157 | revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
158 | version = "v1.0"
159 |
160 | [[projects]]
161 | name = "github.com/json-iterator/go"
162 | packages = ["."]
163 | revision = "6ed27152e0428abfde127acb33b08b03a1e67cac"
164 | version = "1.0.2"
165 |
166 | [[projects]]
167 | branch = "master"
168 | name = "github.com/juju/ratelimit"
169 | packages = ["."]
170 | revision = "5b9ff866471762aa2ab2dced63c9fb6f53921342"
171 |
172 | [[projects]]
173 | branch = "master"
174 | name = "github.com/mailru/easyjson"
175 | packages = ["buffer","jlexer","jwriter"]
176 | revision = "2a92e673c9a6302dd05c3a691ae1f24aef46457d"
177 |
178 | [[projects]]
179 | branch = "master"
180 | name = "github.com/mitchellh/go-homedir"
181 | packages = ["."]
182 | revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
183 |
184 | [[projects]]
185 | branch = "master"
186 | name = "github.com/mitchellh/mapstructure"
187 | packages = ["."]
188 | revision = "d0303fe809921458f417bcf828397a65db30a7e4"
189 |
190 | [[projects]]
191 | branch = "master"
192 | name = "github.com/petar/GoLLRB"
193 | packages = ["llrb"]
194 | revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
195 |
196 | [[projects]]
197 | name = "github.com/peterbourgon/diskv"
198 | packages = ["."]
199 | revision = "5f041e8faa004a95c88a202771f4cc3e991971e6"
200 | version = "v2.0.1"
201 |
202 | [[projects]]
203 | branch = "master"
204 | name = "github.com/pquerna/cachecontrol"
205 | packages = [".","cacheobject"]
206 | revision = "5475d973ea70916980bee28c2b674f3dc3eaed0a"
207 |
208 | [[projects]]
209 | branch = "master"
210 | name = "github.com/sethgrid/pester"
211 | packages = ["."]
212 | revision = "0af5bab1e1ea2860c5aef8e77427bab011d774d8"
213 |
214 | [[projects]]
215 | branch = "master"
216 | name = "github.com/spf13/cobra"
217 | packages = ["."]
218 | revision = "b78744579491c1ceeaaa3b40205e56b0591b93a3"
219 |
220 | [[projects]]
221 | name = "github.com/spf13/pflag"
222 | packages = ["."]
223 | revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
224 | version = "v1.0.0"
225 |
226 | [[projects]]
227 | branch = "master"
228 | name = "golang.org/x/crypto"
229 | packages = ["ed25519","ed25519/internal/edwards25519","ssh/terminal"]
230 | revision = "c84b36c635ad003a10f0c755dff5685ceef18c71"
231 |
232 | [[projects]]
233 | branch = "master"
234 | name = "golang.org/x/net"
235 | packages = ["context","context/ctxhttp","http2","http2/hpack","idna","lex/httplex"]
236 | revision = "0a9397675ba34b2845f758fe3cd68828369c6517"
237 |
238 | [[projects]]
239 | branch = "master"
240 | name = "golang.org/x/oauth2"
241 | packages = [".","internal"]
242 | revision = "bb50c06baba3d0c76f9d125c0719093e315b5b44"
243 |
244 | [[projects]]
245 | branch = "master"
246 | name = "golang.org/x/sys"
247 | packages = ["unix","windows"]
248 | revision = "314a259e304ff91bd6985da2a7149bbf91237993"
249 |
250 | [[projects]]
251 | branch = "master"
252 | name = "golang.org/x/text"
253 | packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable","width"]
254 | revision = "1cbadb444a806fd9430d14ad08967ed91da4fa0a"
255 |
256 | [[projects]]
257 | name = "google.golang.org/appengine"
258 | packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"]
259 | revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
260 | version = "v1.0.0"
261 |
262 | [[projects]]
263 | name = "gopkg.in/inf.v0"
264 | packages = ["."]
265 | revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
266 | version = "v0.9.0"
267 |
268 | [[projects]]
269 | name = "gopkg.in/square/go-jose.v2"
270 | packages = [".","cipher","json"]
271 | revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
272 | version = "v2.1.3"
273 |
274 | [[projects]]
275 | branch = "v2"
276 | name = "gopkg.in/yaml.v2"
277 | packages = ["."]
278 | revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
279 |
280 | [[projects]]
281 | branch = "master"
282 | name = "k8s.io/api"
283 | packages = ["core/v1"]
284 | revision = "81aa34336d28aadc3a8e8da7dfd9258c5157e5e4"
285 |
286 | [[projects]]
287 | branch = "master"
288 | name = "k8s.io/apimachinery"
289 | packages = ["pkg/api/errors","pkg/api/resource","pkg/apis/meta/v1","pkg/conversion","pkg/conversion/queryparams","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer/json","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/clock","pkg/util/errors","pkg/util/framer","pkg/util/intstr","pkg/util/net","pkg/util/runtime","pkg/util/sets","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/reflect"]
290 | revision = "3b05bbfa0a45413bfa184edbf9af617e277962fb"
291 |
292 | [[projects]]
293 | branch = "master"
294 | name = "k8s.io/client-go"
295 | packages = ["pkg/version","rest","rest/watch","tools/auth","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/metrics","transport","util/cert","util/flowcontrol","util/homedir","util/integer"]
296 | revision = "82aa063804cf055e16e8911250f888bc216e8b61"
297 |
298 | [[projects]]
299 | branch = "master"
300 | name = "k8s.io/kube-openapi"
301 | packages = ["pkg/common"]
302 | revision = "abfc5fbe1cf87ee697db107fdfd24c32fe4397a8"
303 |
304 | [solve-meta]
305 | analyzer-name = "dep"
306 | analyzer-version = 1
307 | inputs-digest = "cd3b41ba8f565eb08214ea24e5db1be5ae05b7197a6222bdabb22eced388f544"
308 | solver-name = "gps-cdcl"
309 | solver-version = 1
310 |
--------------------------------------------------------------------------------
/Gopkg.toml:
--------------------------------------------------------------------------------
1 |
2 | # Gopkg.toml example
3 | #
4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
5 | # for detailed Gopkg.toml documentation.
6 | #
7 | # required = ["github.com/user/thing/cmd/thing"]
8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
9 | #
10 | # [[constraint]]
11 | # name = "github.com/user/project"
12 | # version = "1.0.0"
13 | #
14 | # [[constraint]]
15 | # name = "github.com/user/project2"
16 | # branch = "dev"
17 | # source = "github.com/myfork/project2"
18 | #
19 | # [[override]]
20 | # name = "github.com/x/y"
21 | # version = "2.4.0"
22 |
23 |
24 | [[constraint]]
25 | branch = "master"
26 | name = "github.com/coreos/go-oidc"
27 |
28 | [[constraint]]
29 | branch = "master"
30 | name = "github.com/spf13/cobra"
31 |
32 | [[constraint]]
33 | branch = "master"
34 | name = "golang.org/x/oauth2"
35 |
36 | [[constraint]]
37 | branch = "master"
38 | name = "k8s.io/client-go"
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kubernetes Authentication Example
2 | This code is provided verbatim as an example of how to connect to an OIDC
3 | provider and authenticate users before configuring their `kubeconfig`.
4 |
5 | At [Pusher](https://pusher.com), we distribute a copy of this app to our engineers
6 | which sources all required information from Vault and configures their cluster
7 | contexts as well.
8 |
9 | You may also wish to build your own version of this app, sourcing it's
10 | configuration automatically, to improve your user-experience.
11 |
12 | ## Attribution
13 | This project started life as the [Dex example app](https://github.com/coreos/dex/tree/master/cmd/example-app).
14 |
15 | ## Usage
16 | You will need to configure an OIDC application with your Identity Provider.
17 |
18 | The redirect URI should be `http://127.0.0.1:5555/callback` and you will need to
19 | make a note of the issuer URL and the client secret that you set/are given.
20 |
21 | We use Dex so I've included an example Dex config snippet below:
22 | ```
23 | staticClients:
24 | - id: kubernetes
25 | redirectURIs:
26 | - 'http://127.0.0.1:5555/callback'
27 | name: 'Kubernetes API'
28 | secret: c3VwZXJzZWNyZXRzdHJpbmcK
29 | ```
30 |
31 | With this configuration, and a Dex instance running at https://auth.exmaple.com/dex,
32 | the following command will initiate the login flow:
33 | ```
34 | ./k8s-auth-example --client-secret c3VwZXJzZWNyZXRzdHJpbmcK --client-id kubernetes --issuer https://auth.exmaple.com/dex
35 | ```
36 |
37 | ## Building
38 | The application is written in Go using [`dep`](https://github.com/golang/dep)
39 | as the package manager. The following will get you your first build:
40 |
41 | ```
42 | go get git@github.com/pusher/k8s-auth-example
43 | cd $GOPATH/src/github.com/pusher/k8s-auth-example
44 | dep ensure
45 | go build -o k8s-auth
46 | ```
47 |
48 | ## Communication
49 |
50 | * Found a bug? Please open an issue.
51 | * Have a feature request. Please open an issue.
52 | * If you want to contribute, please submit a pull request
53 |
54 | ## Contributing
55 | Please see our [Contributing](CONTRIBUTING.md) guidelines.
56 |
57 | ## License
58 | This project is licensed under Apache 2.0 and a copy of the license is available [here](LICENSE).
59 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "crypto/tls"
7 | "crypto/x509"
8 | "encoding/json"
9 | "fmt"
10 | "io/ioutil"
11 | "log"
12 | "net"
13 | "net/http"
14 | "net/http/httputil"
15 | "net/url"
16 | "os"
17 | "os/exec"
18 | "os/signal"
19 | "runtime"
20 | "syscall"
21 | "time"
22 |
23 | k8s_client "k8s.io/client-go/tools/clientcmd"
24 | k8s_api "k8s.io/client-go/tools/clientcmd/api"
25 |
26 | "github.com/coreos/go-oidc"
27 | "github.com/spf13/cobra"
28 | "golang.org/x/oauth2"
29 | )
30 |
31 | const (
32 | exampleAppState = "login"
33 | )
34 |
35 | type app struct {
36 | clientID string
37 | clientSecret string
38 | redirectURI string
39 | kubeconfig string
40 | debug bool
41 |
42 | verifier *oidc.IDTokenVerifier
43 | provider *oidc.Provider
44 |
45 | // Does the provider use "offline_access" scope to request a refresh token
46 | // or does it use "access_type=offline" (e.g. Google)?
47 | offlineAsScope bool
48 |
49 | client *http.Client
50 | shutdownChan chan bool
51 | }
52 |
53 | type claim struct {
54 | Iss string `json:"iss"`
55 | Sub string `json:"sub"`
56 | Aud string `json:"aud"`
57 | Exp int `json:"exp"`
58 | Iat int `json:"iat"`
59 | AtHash string `json:"at_hash"`
60 | Email string `json:"email"`
61 | EmailVerified bool `json:"email_verified"`
62 | Name string `json:"name"`
63 | }
64 |
65 | // return an HTTP client which trusts the provided root CAs.
66 | func httpClientForRootCAs(rootCAs string) (*http.Client, error) {
67 | tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
68 | rootCABytes, err := ioutil.ReadFile(rootCAs)
69 | if err != nil {
70 | return nil, fmt.Errorf("failed to read root-ca: %v", err)
71 | }
72 | if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) {
73 | return nil, fmt.Errorf("no certs found in root CA file %q", rootCAs)
74 | }
75 | return &http.Client{
76 | Transport: &http.Transport{
77 | TLSClientConfig: &tlsConfig,
78 | Proxy: http.ProxyFromEnvironment,
79 | Dial: (&net.Dialer{
80 | Timeout: 30 * time.Second,
81 | KeepAlive: 30 * time.Second,
82 | }).Dial,
83 | TLSHandshakeTimeout: 10 * time.Second,
84 | ExpectContinueTimeout: 1 * time.Second,
85 | },
86 | }, nil
87 | }
88 |
89 | type debugTransport struct {
90 | t http.RoundTripper
91 | }
92 |
93 | func (d debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
94 | reqDump, err := httputil.DumpRequest(req, true)
95 | if err != nil {
96 | return nil, err
97 | }
98 | log.Printf("%s", reqDump)
99 |
100 | resp, err := d.t.RoundTrip(req)
101 | if err != nil {
102 | return nil, err
103 | }
104 |
105 | respDump, err := httputil.DumpResponse(resp, true)
106 | if err != nil {
107 | resp.Body.Close()
108 | return nil, err
109 | }
110 | log.Printf("%s", respDump)
111 | return resp, nil
112 | }
113 |
114 | func cmd() *cobra.Command {
115 | var (
116 | a app
117 | issuerURL string
118 | listen string
119 | tlsCert string
120 | tlsKey string
121 | rootCAs string
122 | )
123 | c := cobra.Command{
124 | Use: "k8s-auth",
125 | Short: "Authenticates users against OIDC and writes the required kubeconfig.",
126 | Long: "",
127 | RunE: func(cmd *cobra.Command, args []string) error {
128 | u, err := url.Parse(a.redirectURI)
129 | if err != nil {
130 | return fmt.Errorf("parse redirect-uri: %v", err)
131 | }
132 | listenURL, err := url.Parse(listen)
133 | if err != nil {
134 | return fmt.Errorf("parse listen address: %v", err)
135 | }
136 |
137 | if rootCAs != "" {
138 | client, cErr := httpClientForRootCAs(rootCAs)
139 | if cErr != nil {
140 | return cErr
141 | }
142 | a.client = client
143 | }
144 |
145 | if a.debug {
146 | if a.client == nil {
147 | a.client = &http.Client{
148 | Transport: debugTransport{http.DefaultTransport},
149 | }
150 | } else {
151 | a.client.Transport = debugTransport{a.client.Transport}
152 | }
153 | }
154 |
155 | if a.client == nil {
156 | a.client = http.DefaultClient
157 | }
158 |
159 | ctx := oidc.ClientContext(context.Background(), a.client)
160 | provider, err := oidc.NewProvider(ctx, issuerURL)
161 | if err != nil {
162 | return fmt.Errorf("Failed to query provider %q: %v", issuerURL, err)
163 | }
164 |
165 | var s struct {
166 | // What scopes does a provider support?
167 | //
168 | // See: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
169 | ScopesSupported []string `json:"scopes_supported"`
170 | }
171 | if err := provider.Claims(&s); err != nil {
172 | return fmt.Errorf("Failed to parse provider scopes_supported: %v", err)
173 | }
174 |
175 | if len(s.ScopesSupported) == 0 {
176 | // scopes_supported is a "RECOMMENDED" discovery claim, not a required
177 | // one. If missing, assume that the provider follows the spec and has
178 | // an "offline_access" scope.
179 | a.offlineAsScope = true
180 | } else {
181 | // See if scopes_supported has the "offline_access" scope.
182 | a.offlineAsScope = func() bool {
183 | for _, scope := range s.ScopesSupported {
184 | if scope == oidc.ScopeOfflineAccess {
185 | return true
186 | }
187 | }
188 | return false
189 | }()
190 | }
191 |
192 | a.provider = provider
193 | a.verifier = provider.Verifier(&oidc.Config{ClientID: a.clientID})
194 | a.shutdownChan = make(chan bool)
195 |
196 | http.HandleFunc("/", a.handleLogin)
197 | http.HandleFunc(u.Path, a.handleCallback)
198 |
199 | switch listenURL.Scheme {
200 | case "http":
201 | log.Printf("listening on %s", listen)
202 | go open(listen)
203 | go a.waitShutdown()
204 | return http.ListenAndServe(listenURL.Host, nil)
205 | case "https":
206 | log.Printf("listening on %s", listen)
207 | go open(listen)
208 | go a.waitShutdown()
209 | return http.ListenAndServeTLS(listenURL.Host, tlsCert, tlsKey, nil)
210 | default:
211 | return fmt.Errorf("listen address %q is not using http or https", listen)
212 | }
213 | },
214 | }
215 |
216 | // Configurable variables
217 | c.Flags().StringVar(&a.clientID, "client-id", "kubernetes", "OAuth2 client ID of this application.")
218 | c.Flags().StringVar(&a.clientSecret, "client-secret", "c3VwZXJzZWNyZXRzdHJpbmcK", "OAuth2 client secret of this application.")
219 | c.Flags().StringVar(&a.redirectURI, "redirect-uri", "http://127.0.0.1:5555/callback", "Callback URL for OAuth2 responses.")
220 | c.Flags().StringVar(&issuerURL, "issuer", "http://127.0.0.1:5556/dex", "URL of the OpenID Connect issuer.")
221 | c.Flags().StringVar(&listen, "listen", "http://127.0.0.1:5555", "HTTP(S) address to listen at.")
222 | c.Flags().StringVar(&tlsCert, "tls-cert", "", "X509 cert file to present when serving HTTPS.")
223 | c.Flags().StringVar(&tlsKey, "tls-key", "", "Private key for the HTTPS cert.")
224 | c.Flags().StringVar(&rootCAs, "issuer-root-ca", "", "Root certificate authorities for the issuer. Defaults to host certs.")
225 | c.Flags().BoolVar(&a.debug, "debug", false, "Print all request and responses from the OpenID Connect issuer.")
226 | c.Flags().StringVar(&a.kubeconfig, "kubeconfig", "", "Kubeconfig file to configure")
227 | return &c
228 | }
229 |
230 | func main() {
231 | if err := cmd().Execute(); err != nil {
232 | fmt.Fprintf(os.Stderr, "error: %v\n", err)
233 | os.Exit(2)
234 | }
235 | }
236 |
237 | func (a *app) oauth2Config(scopes []string) *oauth2.Config {
238 | return &oauth2.Config{
239 | ClientID: a.clientID,
240 | ClientSecret: a.clientSecret,
241 | Endpoint: a.provider.Endpoint(),
242 | Scopes: scopes,
243 | RedirectURL: a.redirectURI,
244 | }
245 | }
246 |
247 | func (a *app) handleLogin(w http.ResponseWriter, r *http.Request) {
248 | var scopes []string
249 |
250 | var authCodeURL string
251 | scopes = append(scopes, "groups", "openid", "profile", "email")
252 | if a.offlineAsScope {
253 | scopes = append(scopes, "offline_access")
254 | authCodeURL = a.oauth2Config(scopes).AuthCodeURL(exampleAppState)
255 | } else {
256 | authCodeURL = a.oauth2Config(scopes).AuthCodeURL(exampleAppState, oauth2.AccessTypeOffline)
257 | }
258 |
259 | http.Redirect(w, r, authCodeURL, http.StatusSeeOther)
260 | }
261 |
262 | func (a *app) handleCallback(w http.ResponseWriter, r *http.Request) {
263 | var (
264 | err error
265 | token *oauth2.Token
266 | )
267 |
268 | ctx := oidc.ClientContext(r.Context(), a.client)
269 | oauth2Config := a.oauth2Config(nil)
270 | switch r.Method {
271 | case "GET":
272 | // Authorization redirect callback from OAuth2 auth flow.
273 | if errMsg := r.FormValue("error"); errMsg != "" {
274 | http.Error(w, errMsg+": "+r.FormValue("error_description"), http.StatusBadRequest)
275 | return
276 | }
277 | code := r.FormValue("code")
278 | if code == "" {
279 | http.Error(w, fmt.Sprintf("no code in request: %q", r.Form), http.StatusBadRequest)
280 | return
281 | }
282 | if state := r.FormValue("state"); state != exampleAppState {
283 | http.Error(w, fmt.Sprintf("expected state %q got %q", exampleAppState, state), http.StatusBadRequest)
284 | return
285 | }
286 | token, err = oauth2Config.Exchange(ctx, code)
287 | case "POST":
288 | // Form request from frontend to refresh a token.
289 | refresh := r.FormValue("refresh_token")
290 | if refresh == "" {
291 | http.Error(w, fmt.Sprintf("no refresh_token in request: %q", r.Form), http.StatusBadRequest)
292 | return
293 | }
294 | t := &oauth2.Token{
295 | RefreshToken: refresh,
296 | Expiry: time.Now().Add(-time.Hour),
297 | }
298 | token, err = oauth2Config.TokenSource(ctx, t).Token()
299 | default:
300 | http.Error(w, fmt.Sprintf("method not implemented: %s", r.Method), http.StatusBadRequest)
301 | return
302 | }
303 |
304 | if err != nil {
305 | http.Error(w, fmt.Sprintf("failed to get token: %v", err), http.StatusInternalServerError)
306 | return
307 | }
308 |
309 | rawIDToken, ok := token.Extra("id_token").(string)
310 | if !ok {
311 | http.Error(w, "no id_token in token response", http.StatusInternalServerError)
312 | return
313 | }
314 |
315 | idToken, err := a.verifier.Verify(r.Context(), rawIDToken)
316 | if err != nil {
317 | http.Error(w, fmt.Sprintf("Failed to verify ID token: %v", err), http.StatusInternalServerError)
318 | return
319 | }
320 | var claims json.RawMessage
321 | idToken.Claims(&claims)
322 |
323 | buff := new(bytes.Buffer)
324 | json.Indent(buff, []byte(claims), "", " ")
325 | var m claim
326 | err = json.Unmarshal(claims, &m)
327 | if err != nil {
328 | http.Error(w, fmt.Sprintf("Failed to read claims: %v", err), http.StatusInternalServerError)
329 | go func() {
330 | a.shutdownChan <- true
331 | }()
332 | return
333 | }
334 |
335 | err = updateKubeConfig(rawIDToken, token.RefreshToken, m, a)
336 | if err != nil {
337 | http.Error(w, fmt.Sprintf("Failed to update kubeconfig: %v", err), http.StatusInternalServerError)
338 | go func() {
339 | a.shutdownChan <- true
340 | }()
341 | return
342 | }
343 |
344 | renderToken(w, a.redirectURI, rawIDToken, token.RefreshToken, buff.Bytes(), a.debug)
345 | fmt.Printf("Login Succeeded as %s\n", m.Email)
346 | if a.debug {
347 | fmt.Printf("ID Token: %s\n", rawIDToken)
348 | fmt.Printf("Refresh Token: %s\n", token.RefreshToken)
349 | fmt.Printf("Claims: %s\n", string(claims))
350 | }
351 |
352 | go func() {
353 | a.shutdownChan <- true
354 | }()
355 | }
356 |
357 | func (a *app) waitShutdown() {
358 | irqSig := make(chan os.Signal, 1)
359 | signal.Notify(irqSig, syscall.SIGINT, syscall.SIGTERM)
360 |
361 | //Wait interrupt or shutdown request through /shutdown
362 | select {
363 | case sig := <-irqSig:
364 | log.Printf("Shutdown request (signal: %v)", sig)
365 | os.Exit(0)
366 | case <-a.shutdownChan:
367 | os.Exit(0)
368 | }
369 | }
370 |
371 | func updateKubeConfig(IDToken string, refreshToken string, claims claim, a *app) error {
372 | var config *k8s_api.Config
373 | var outputFilename string
374 | var err error
375 |
376 | clientConfigLoadingRules := k8s_client.NewDefaultClientConfigLoadingRules()
377 |
378 | if a.kubeconfig != "" {
379 | if _, err = os.Stat(a.kubeconfig); os.IsNotExist(err) {
380 | config = k8s_api.NewConfig()
381 | err = nil
382 | } else {
383 | clientConfigLoadingRules.ExplicitPath = a.kubeconfig
384 | config, err = clientConfigLoadingRules.Load()
385 | }
386 | outputFilename = a.kubeconfig
387 | } else {
388 | config, err = clientConfigLoadingRules.Load()
389 | outputFilename = k8s_client.RecommendedHomeFile
390 | if !k8s_api.IsConfigEmpty(config) {
391 | outputFilename = clientConfigLoadingRules.GetDefaultFilename()
392 | }
393 | }
394 | if err != nil {
395 | return err
396 | }
397 |
398 | authInfo := k8s_api.NewAuthInfo()
399 | if conf, ok := config.AuthInfos[claims.Email]; ok {
400 | authInfo = conf
401 | }
402 |
403 | authInfo.AuthProvider = &k8s_api.AuthProviderConfig{
404 | Name: "oidc",
405 | Config: map[string]string{
406 | "client-id": a.clientID,
407 | "client-secret": a.clientSecret,
408 | "id-token": IDToken,
409 | "refresh-token": refreshToken,
410 | "idp-issuer-url": claims.Iss,
411 | },
412 | }
413 |
414 | config.AuthInfos[claims.Email] = authInfo
415 |
416 | fmt.Printf("Writing config to %s\n", outputFilename)
417 | err = k8s_client.WriteToFile(*config, outputFilename)
418 | if err != nil {
419 | return err
420 | }
421 | return nil
422 | }
423 |
424 | func open(url string) error {
425 |
426 | var cmd string
427 | var args []string
428 |
429 | switch runtime.GOOS {
430 | case "windows":
431 | cmd = "cmd"
432 | args = []string{"/c", "start"}
433 | case "darwin":
434 | cmd = "open"
435 | default: // "linux", "freebsd", "openbsd", "netbsd"
436 | cmd = "xdg-open"
437 | }
438 | args = append(args, url)
439 | return exec.Command(cmd, args...).Start()
440 | }
441 |
--------------------------------------------------------------------------------
/templates.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "html/template"
5 | "log"
6 | "net/http"
7 | )
8 |
9 | type tokenTmplData struct {
10 | IDToken string
11 | RefreshToken string
12 | RedirectURL string
13 | Claims string
14 | Debug bool
15 | }
16 |
17 | var tokenTmpl = template.Must(template.New("token.html").Parse(`
18 |
19 |
29 |
30 |
31 | Congratulations! You have successfully authenticated. Please close this page and return to your terminal.
32 | {{ if .Debug }}
33 | Token:
{{ .IDToken }}
34 | Claims:
{{ .Claims }}
35 | {{ if .RefreshToken }}
36 | Refresh Token:
{{ .RefreshToken }}
37 | {{ end }}
38 | {{ end }}
39 |
40 |
41 | `))
42 |
43 | func renderToken(w http.ResponseWriter, redirectURL, idToken, refreshToken string, claims []byte, debug bool) {
44 | renderTemplate(w, tokenTmpl, tokenTmplData{
45 | IDToken: idToken,
46 | RefreshToken: refreshToken,
47 | RedirectURL: redirectURL,
48 | Claims: string(claims),
49 | Debug: debug,
50 | })
51 | }
52 |
53 | func renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) {
54 | err := tmpl.Execute(w, data)
55 | if err == nil {
56 | return
57 | }
58 |
59 | switch err := err.(type) {
60 | case *template.Error:
61 | // An ExecError guarantees that Execute has not written to the underlying reader.
62 | log.Printf("Error rendering template %s: %s", tmpl.Name(), err)
63 |
64 | // TODO(ericchiang): replace with better internal server error.
65 | http.Error(w, "Internal server error", http.StatusInternalServerError)
66 | default:
67 | // An error with the underlying write, such as the connection being
68 | // dropped. Ignore for now.
69 | }
70 | }
71 |
--------------------------------------------------------------------------------