istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/credentials/kube/secrets.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kube 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "strings" 22 "sync" 23 "time" 24 25 authorizationv1 "k8s.io/api/authorization/v1" 26 v1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/fields" 29 sa "k8s.io/apiserver/pkg/authentication/serviceaccount" 30 authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1" 31 32 "istio.io/istio/pilot/pkg/credentials" 33 securitymodel "istio.io/istio/pilot/pkg/security/model" 34 "istio.io/istio/pkg/kube" 35 "istio.io/istio/pkg/kube/controllers" 36 "istio.io/istio/pkg/kube/kclient" 37 "istio.io/istio/pkg/log" 38 ) 39 40 const ( 41 // The ID/name for the certificate chain in kubernetes generic secret. 42 GenericScrtCert = "cert" 43 // The ID/name for the private key in kubernetes generic secret. 44 GenericScrtKey = "key" 45 // The ID/name for the CA certificate in kubernetes generic secret. 46 GenericScrtCaCert = "cacert" 47 // The ID/name for the CRL in kubernetes generic secret. 48 GenericScrtCRL = "crl" 49 50 // The ID/name for the certificate chain in kubernetes tls secret. 51 TLSSecretCert = "tls.crt" 52 // The ID/name for the k8sKey in kubernetes tls secret. 53 TLSSecretKey = "tls.key" 54 // The ID/name for the certificate OCSP staple in kubernetes tls secret 55 TLSSecretOcspStaple = "tls.ocsp-staple" 56 // The ID/name for the CA certificate in kubernetes tls secret 57 TLSSecretCaCert = "ca.crt" 58 // The ID/name for the CRL in kubernetes tls secret. 59 TLSSecretCrl = "ca.crl" 60 ) 61 62 type CredentialsController struct { 63 secrets kclient.Client[*v1.Secret] 64 sar authorizationv1client.SubjectAccessReviewInterface 65 66 mu sync.RWMutex 67 authorizationCache map[authorizationKey]authorizationResponse 68 } 69 70 type authorizationKey string 71 72 type authorizationResponse struct { 73 expiration time.Time 74 authorized error 75 } 76 77 var _ credentials.Controller = &CredentialsController{} 78 79 func NewCredentialsController(kc kube.Client, handlers []func(name string, namespace string)) *CredentialsController { 80 // We only care about TLS certificates and docker config for Wasm image pulling. 81 // Unfortunately, it is not as simple as selecting type=kubernetes.io/tls and type=kubernetes.io/dockerconfigjson. 82 // Because of legacy reasons and supporting an extra ca.crt, we also support generic types. 83 // Its also likely users have started to use random types and expect them to continue working. 84 // This makes the assumption we will never care about Helm secrets or SA token secrets - two common 85 // large secrets in clusters. 86 // This is a best effort optimization only; the code would behave correctly if we watched all secrets. 87 fieldSelector := fields.AndSelectors( 88 fields.OneTermNotEqualSelector("type", "helm.sh/release.v1"), 89 fields.OneTermNotEqualSelector("type", string(v1.SecretTypeServiceAccountToken))).String() 90 secrets := kclient.NewFiltered[*v1.Secret](kc, kclient.Filter{ 91 FieldSelector: fieldSelector, 92 }) 93 94 for _, h := range handlers { 95 h := h 96 // register handler before informer starts 97 secrets.AddEventHandler(controllers.ObjectHandler(func(o controllers.Object) { 98 h(o.GetName(), o.GetNamespace()) 99 })) 100 } 101 102 return &CredentialsController{ 103 secrets: secrets, 104 sar: kc.Kube().AuthorizationV1().SubjectAccessReviews(), 105 authorizationCache: make(map[authorizationKey]authorizationResponse), 106 } 107 } 108 109 func (s *CredentialsController) Close() { 110 s.secrets.ShutdownHandlers() 111 } 112 113 func (s *CredentialsController) HasSynced() bool { 114 return s.secrets.HasSynced() 115 } 116 117 const cacheTTL = time.Minute 118 119 // clearExpiredCache iterates through the cache and removes all expired entries. Should be called with mutex held. 120 func (s *CredentialsController) clearExpiredCache() { 121 for k, v := range s.authorizationCache { 122 if v.expiration.Before(time.Now()) { 123 delete(s.authorizationCache, k) 124 } 125 } 126 } 127 128 // cachedAuthorization checks the authorization cache 129 // nolint 130 func (s *CredentialsController) cachedAuthorization(user string) (error, bool) { 131 key := authorizationKey(user) 132 s.mu.Lock() 133 defer s.mu.Unlock() 134 s.clearExpiredCache() 135 // No need to check expiration, we will evict expired entries above 136 got, f := s.authorizationCache[key] 137 if !f { 138 return nil, false 139 } 140 return got.authorized, true 141 } 142 143 // cachedAuthorization checks the authorization cache 144 func (s *CredentialsController) insertCache(user string, response error) { 145 s.mu.Lock() 146 defer s.mu.Unlock() 147 key := authorizationKey(user) 148 expDelta := cacheTTL 149 if response == nil { 150 // Cache success a bit longer, there is no need to quickly revoke access 151 expDelta *= 5 152 } 153 log.Debugf("cached authorization for user %s: %v", user, response) 154 s.authorizationCache[key] = authorizationResponse{ 155 expiration: time.Now().Add(expDelta), 156 authorized: response, 157 } 158 } 159 160 func (s *CredentialsController) Authorize(serviceAccount, namespace string) error { 161 user := sa.MakeUsername(namespace, serviceAccount) 162 if cached, f := s.cachedAuthorization(user); f { 163 return cached 164 } 165 err := func() error { 166 resp, err := s.sar.Create(context.Background(), &authorizationv1.SubjectAccessReview{ 167 ObjectMeta: metav1.ObjectMeta{}, 168 Spec: authorizationv1.SubjectAccessReviewSpec{ 169 ResourceAttributes: &authorizationv1.ResourceAttributes{ 170 Namespace: namespace, 171 Verb: "list", 172 Resource: "secrets", 173 }, 174 User: user, 175 }, 176 }, metav1.CreateOptions{}) 177 if err != nil { 178 return err 179 } 180 if !resp.Status.Allowed { 181 return fmt.Errorf("%s/%s is not authorized to read secrets: %v", serviceAccount, namespace, resp.Status.Reason) 182 } 183 return nil 184 }() 185 s.insertCache(user, err) 186 return err 187 } 188 189 func (s *CredentialsController) GetCertInfo(name, namespace string) (certInfo *credentials.CertInfo, err error) { 190 k8sSecret := s.secrets.Get(name, namespace) 191 if k8sSecret == nil { 192 return nil, fmt.Errorf("secret %v/%v not found", namespace, name) 193 } 194 195 return ExtractCertInfo(k8sSecret) 196 } 197 198 func (s *CredentialsController) GetCaCert(name, namespace string) (certInfo *credentials.CertInfo, err error) { 199 k8sSecret := s.secrets.Get(name, namespace) 200 if k8sSecret == nil { 201 strippedName := strings.TrimSuffix(name, securitymodel.SdsCaSuffix) 202 // Could not fetch cert, look for secret without -cacert suffix 203 k8sSecret := s.secrets.Get(strippedName, namespace) 204 if k8sSecret == nil { 205 return nil, fmt.Errorf("secret %v/%v not found", namespace, strippedName) 206 } 207 return extractRoot(k8sSecret) 208 } 209 return extractRoot(k8sSecret) 210 } 211 212 func (s *CredentialsController) GetDockerCredential(name, namespace string) ([]byte, error) { 213 k8sSecret := s.secrets.Get(name, namespace) 214 if k8sSecret == nil { 215 return nil, fmt.Errorf("secret %v/%v not found", namespace, name) 216 } 217 if k8sSecret.Type != v1.SecretTypeDockerConfigJson { 218 return nil, fmt.Errorf("type of secret %v/%v is not %v", namespace, name, v1.SecretTypeDockerConfigJson) 219 } 220 if cred, found := k8sSecret.Data[v1.DockerConfigJsonKey]; found { 221 return cred, nil 222 } 223 return nil, fmt.Errorf("cannot find docker config at secret %v/%v", namespace, name) 224 } 225 226 func hasKeys(d map[string][]byte, keys ...string) bool { 227 for _, k := range keys { 228 _, f := d[k] 229 if !f { 230 return false 231 } 232 } 233 return true 234 } 235 236 func hasValue(d map[string][]byte, keys ...string) bool { 237 for _, k := range keys { 238 v := d[k] 239 if len(v) == 0 { 240 return false 241 } 242 } 243 return true 244 } 245 246 // ExtractCertInfo extracts server key, certificate, and OCSP staple 247 func ExtractCertInfo(scrt *v1.Secret) (certInfo *credentials.CertInfo, err error) { 248 ret := &credentials.CertInfo{} 249 if hasValue(scrt.Data, GenericScrtCert, GenericScrtKey) { 250 ret.Cert = scrt.Data[GenericScrtCert] 251 ret.Key = scrt.Data[GenericScrtKey] 252 ret.CRL = scrt.Data[GenericScrtCRL] 253 return ret, nil 254 } 255 if hasValue(scrt.Data, TLSSecretCert, TLSSecretKey) { 256 ret.Cert = scrt.Data[TLSSecretCert] 257 ret.Key = scrt.Data[TLSSecretKey] 258 ret.Staple = scrt.Data[TLSSecretOcspStaple] 259 ret.CRL = scrt.Data[TLSSecretCrl] 260 return ret, nil 261 } 262 // No cert found. Try to generate a helpful error message 263 if hasKeys(scrt.Data, GenericScrtCert, GenericScrtKey) { 264 return nil, fmt.Errorf("found keys %q and %q, but they were empty", GenericScrtCert, GenericScrtKey) 265 } 266 if hasKeys(scrt.Data, TLSSecretCert, TLSSecretKey) { 267 return nil, fmt.Errorf("found keys %q and %q, but they were empty", TLSSecretCert, TLSSecretKey) 268 } 269 found := truncatedKeysMessage(scrt.Data) 270 return nil, fmt.Errorf("found secret, but didn't have expected keys (%s and %s) or (%s and %s); found: %s", 271 GenericScrtCert, GenericScrtKey, TLSSecretCert, TLSSecretKey, found) 272 } 273 274 func truncatedKeysMessage(data map[string][]byte) string { 275 keys := []string{} 276 for k := range data { 277 keys = append(keys, k) 278 } 279 sort.Strings(keys) 280 if len(keys) < 3 { 281 return strings.Join(keys, ", ") 282 } 283 return fmt.Sprintf("%s, and %d more...", strings.Join(keys[:3], ", "), len(keys)-3) 284 } 285 286 // extractRoot extracts the root certificate 287 func extractRoot(scrt *v1.Secret) (certInfo *credentials.CertInfo, err error) { 288 ret := &credentials.CertInfo{} 289 if hasValue(scrt.Data, GenericScrtCaCert) { 290 ret.Cert = scrt.Data[GenericScrtCaCert] 291 ret.CRL = scrt.Data[GenericScrtCRL] 292 return ret, nil 293 } 294 if hasValue(scrt.Data, TLSSecretCaCert) { 295 ret.Cert = scrt.Data[TLSSecretCaCert] 296 ret.CRL = scrt.Data[TLSSecretCrl] 297 return ret, nil 298 } 299 // No cert found. Try to generate a helpful error message 300 if hasKeys(scrt.Data, GenericScrtCaCert) { 301 return nil, fmt.Errorf("found key %q, but it was empty", GenericScrtCaCert) 302 } 303 if hasKeys(scrt.Data, TLSSecretCaCert) { 304 return nil, fmt.Errorf("found key %q, but it was empty", TLSSecretCaCert) 305 } 306 found := truncatedKeysMessage(scrt.Data) 307 return nil, fmt.Errorf("found secret, but didn't have expected keys %s or %s; found: %s", 308 GenericScrtCaCert, TLSSecretCaCert, found) 309 }