|
| 1 | +/* |
| 2 | +Copyright 2019 The Kubernetes Authors. |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
| 17 | +package routes |
| 18 | + |
| 19 | +import ( |
| 20 | + "net/http" |
| 21 | + |
| 22 | + restful "github.com/emicklei/go-restful" |
| 23 | + |
| 24 | + "k8s.io/klog" |
| 25 | + "k8s.io/kubernetes/pkg/serviceaccount" |
| 26 | +) |
| 27 | + |
| 28 | +// This code is in package routes because many controllers import |
| 29 | +// pkg/serviceaccount, but are not allowed by import-boss to depend on |
| 30 | +// go-restful. All logic that deals with keys is kept in pkg/serviceaccount, |
| 31 | +// and only the rendered JSON is passed into this server. |
| 32 | + |
| 33 | +const ( |
| 34 | + // cacheControl is the value of the Cache-Control header. Overrides the |
| 35 | + // global `private, no-cache` setting. |
| 36 | + headerCacheControl = "Cache-Control" |
| 37 | + cacheControl = "public, max-age=3600" // 1 hour |
| 38 | + |
| 39 | + // mimeJWKS is the content type of the keyset response |
| 40 | + mimeJWKS = "application/jwk-set+json" |
| 41 | +) |
| 42 | + |
| 43 | +// OpenIDMetadataServer is an HTTP server for metadata of the KSA token issuer. |
| 44 | +type OpenIDMetadataServer struct { |
| 45 | + configJSON []byte |
| 46 | + keysetJSON []byte |
| 47 | +} |
| 48 | + |
| 49 | +// NewOpenIDMetadataServer creates a new OpenIDMetadataServer. |
| 50 | +// The issuer is the OIDC issuer; keys are the keys that may be used to sign |
| 51 | +// KSA tokens. |
| 52 | +func NewOpenIDMetadataServer(configJSON, keysetJSON []byte) *OpenIDMetadataServer { |
| 53 | + return &OpenIDMetadataServer{ |
| 54 | + configJSON: configJSON, |
| 55 | + keysetJSON: keysetJSON, |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +// Install adds this server to the request router c. |
| 60 | +func (s *OpenIDMetadataServer) Install(c *restful.Container) { |
| 61 | + // Configuration WebService |
| 62 | + // Container.Add "will detect duplicate root paths and exit in that case", |
| 63 | + // so we need a root for /.well-known/openid-configuration to avoid conflicts. |
| 64 | + cfg := new(restful.WebService). |
| 65 | + Produces(restful.MIME_JSON) |
| 66 | + |
| 67 | + cfg.Path(serviceaccount.OpenIDConfigPath).Route( |
| 68 | + cfg.GET(""). |
| 69 | + To(fromStandard(s.serveConfiguration)). |
| 70 | + Doc("get service account issuer OpenID configuration, also known as the 'OIDC discovery doc'"). |
| 71 | + Operation("getServiceAccountIssuerOpenIDConfiguration"). |
| 72 | + // Just include the OK, doesn't look like we include Internal Error in our openapi-spec. |
| 73 | + Returns(http.StatusOK, "OK", "")) |
| 74 | + c.Add(cfg) |
| 75 | + |
| 76 | + // JWKS WebService |
| 77 | + jwks := new(restful.WebService). |
| 78 | + Produces(mimeJWKS) |
| 79 | + |
| 80 | + jwks.Path(serviceaccount.JWKSPath).Route( |
| 81 | + jwks.GET(""). |
| 82 | + To(fromStandard(s.serveKeys)). |
| 83 | + Doc("get service account issuer OpenID JSON Web Key Set (contains public token verification keys)"). |
| 84 | + Operation("getServiceAccountIssuerOpenIDKeyset"). |
| 85 | + // Just include the OK, doesn't look like we include Internal Error in our openapi-spec. |
| 86 | + Returns(http.StatusOK, "OK", "")) |
| 87 | + c.Add(jwks) |
| 88 | +} |
| 89 | + |
| 90 | +// fromStandard provides compatibility between the standard (net/http) handler signature and the restful signature. |
| 91 | +func fromStandard(h http.HandlerFunc) restful.RouteFunction { |
| 92 | + return func(req *restful.Request, resp *restful.Response) { |
| 93 | + h(resp, req.Request) |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +func (s *OpenIDMetadataServer) serveConfiguration(w http.ResponseWriter, req *http.Request) { |
| 98 | + w.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON) |
| 99 | + w.Header().Set(headerCacheControl, cacheControl) |
| 100 | + if _, err := w.Write(s.configJSON); err != nil { |
| 101 | + klog.Errorf("failed to write service account issuer metadata response: %v", err) |
| 102 | + return |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +func (s *OpenIDMetadataServer) serveKeys(w http.ResponseWriter, req *http.Request) { |
| 107 | + // Per RFC7517 : https://tools.ietf.org/html/rfc7517#section-8.5.1 |
| 108 | + w.Header().Set(restful.HEADER_ContentType, mimeJWKS) |
| 109 | + w.Header().Set(headerCacheControl, cacheControl) |
| 110 | + if _, err := w.Write(s.keysetJSON); err != nil { |
| 111 | + klog.Errorf("failed to write service account issuer JWKS response: %v", err) |
| 112 | + return |
| 113 | + } |
| 114 | +} |
0 commit comments