k8s.io/apiserver@v0.31.1/pkg/authentication/serviceaccount/util.go (about) 1 /* 2 Copyright 2014 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 serviceaccount 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 24 v1 "k8s.io/api/core/v1" 25 apierrors "k8s.io/apimachinery/pkg/api/errors" 26 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apiserver/pkg/authentication/user" 29 v1core "k8s.io/client-go/kubernetes/typed/core/v1" 30 31 "k8s.io/klog/v2" 32 ) 33 34 const ( 35 ServiceAccountUsernamePrefix = "system:serviceaccount:" 36 ServiceAccountUsernameSeparator = ":" 37 ServiceAccountGroupPrefix = "system:serviceaccounts:" 38 AllServiceAccountsGroup = "system:serviceaccounts" 39 // CredentialIDKey is the key used in a user's "extra" to specify the unique 40 // identifier for this identity document). 41 CredentialIDKey = "authentication.kubernetes.io/credential-id" 42 // IssuedCredentialIDAuditAnnotationKey is the annotation key used in the audit event that is persisted to the 43 // '/token' endpoint for service accounts. 44 // This annotation indicates the generated credential identifier for the service account token being issued. 45 // This is useful when tracing back the origin of tokens that have gone on to make request that have persisted 46 // their credential-identifier into the audit log via the user's extra info stored on subsequent audit events. 47 IssuedCredentialIDAuditAnnotationKey = "authentication.kubernetes.io/issued-credential-id" 48 // PodNameKey is the key used in a user's "extra" to specify the pod name of 49 // the authenticating request. 50 PodNameKey = "authentication.kubernetes.io/pod-name" 51 // PodUIDKey is the key used in a user's "extra" to specify the pod UID of 52 // the authenticating request. 53 PodUIDKey = "authentication.kubernetes.io/pod-uid" 54 // NodeNameKey is the key used in a user's "extra" to specify the node name of 55 // the authenticating request. 56 NodeNameKey = "authentication.kubernetes.io/node-name" 57 // NodeUIDKey is the key used in a user's "extra" to specify the node UID of 58 // the authenticating request. 59 NodeUIDKey = "authentication.kubernetes.io/node-uid" 60 ) 61 62 // MakeUsername generates a username from the given namespace and ServiceAccount name. 63 // The resulting username can be passed to SplitUsername to extract the original namespace and ServiceAccount name. 64 func MakeUsername(namespace, name string) string { 65 return ServiceAccountUsernamePrefix + namespace + ServiceAccountUsernameSeparator + name 66 } 67 68 // MatchesUsername checks whether the provided username matches the namespace and name without 69 // allocating. Use this when checking a service account namespace and name against a known string. 70 func MatchesUsername(namespace, name string, username string) bool { 71 if !strings.HasPrefix(username, ServiceAccountUsernamePrefix) { 72 return false 73 } 74 username = username[len(ServiceAccountUsernamePrefix):] 75 76 if !strings.HasPrefix(username, namespace) { 77 return false 78 } 79 username = username[len(namespace):] 80 81 if !strings.HasPrefix(username, ServiceAccountUsernameSeparator) { 82 return false 83 } 84 username = username[len(ServiceAccountUsernameSeparator):] 85 86 return username == name 87 } 88 89 var invalidUsernameErr = fmt.Errorf("Username must be in the form %s", MakeUsername("namespace", "name")) 90 91 // SplitUsername returns the namespace and ServiceAccount name embedded in the given username, 92 // or an error if the username is not a valid name produced by MakeUsername 93 func SplitUsername(username string) (string, string, error) { 94 if !strings.HasPrefix(username, ServiceAccountUsernamePrefix) { 95 return "", "", invalidUsernameErr 96 } 97 trimmed := strings.TrimPrefix(username, ServiceAccountUsernamePrefix) 98 parts := strings.Split(trimmed, ServiceAccountUsernameSeparator) 99 if len(parts) != 2 { 100 return "", "", invalidUsernameErr 101 } 102 namespace, name := parts[0], parts[1] 103 if len(apimachineryvalidation.ValidateNamespaceName(namespace, false)) != 0 { 104 return "", "", invalidUsernameErr 105 } 106 if len(apimachineryvalidation.ValidateServiceAccountName(name, false)) != 0 { 107 return "", "", invalidUsernameErr 108 } 109 return namespace, name, nil 110 } 111 112 // MakeGroupNames generates service account group names for the given namespace 113 func MakeGroupNames(namespace string) []string { 114 return []string{ 115 AllServiceAccountsGroup, 116 MakeNamespaceGroupName(namespace), 117 } 118 } 119 120 // MakeNamespaceGroupName returns the name of the group all service accounts in the namespace are included in 121 func MakeNamespaceGroupName(namespace string) string { 122 return ServiceAccountGroupPrefix + namespace 123 } 124 125 // UserInfo returns a user.Info interface for the given namespace, service account name and UID 126 func UserInfo(namespace, name, uid string) user.Info { 127 return (&ServiceAccountInfo{ 128 Name: name, 129 Namespace: namespace, 130 UID: uid, 131 }).UserInfo() 132 } 133 134 type ServiceAccountInfo struct { 135 Name, Namespace, UID string 136 PodName, PodUID string 137 CredentialID string 138 NodeName, NodeUID string 139 } 140 141 func (sa *ServiceAccountInfo) UserInfo() user.Info { 142 info := &user.DefaultInfo{ 143 Name: MakeUsername(sa.Namespace, sa.Name), 144 UID: sa.UID, 145 Groups: MakeGroupNames(sa.Namespace), 146 } 147 148 if sa.PodName != "" && sa.PodUID != "" { 149 if info.Extra == nil { 150 info.Extra = make(map[string][]string) 151 } 152 info.Extra[PodNameKey] = []string{sa.PodName} 153 info.Extra[PodUIDKey] = []string{sa.PodUID} 154 } 155 if sa.CredentialID != "" { 156 if info.Extra == nil { 157 info.Extra = make(map[string][]string) 158 } 159 info.Extra[CredentialIDKey] = []string{sa.CredentialID} 160 } 161 if sa.NodeName != "" { 162 if info.Extra == nil { 163 info.Extra = make(map[string][]string) 164 } 165 info.Extra[NodeNameKey] = []string{sa.NodeName} 166 // node UID is optional and will only be set if the node name is set 167 if sa.NodeUID != "" { 168 info.Extra[NodeUIDKey] = []string{sa.NodeUID} 169 } 170 } 171 172 return info 173 } 174 175 // CredentialIDForJTI converts a given JTI string into a credential identifier for use in a 176 // users 'extra' info. 177 func CredentialIDForJTI(jti string) string { 178 if len(jti) == 0 { 179 return "" 180 } 181 return "JTI=" + jti 182 } 183 184 // IsServiceAccountToken returns true if the secret is a valid api token for the service account 185 func IsServiceAccountToken(secret *v1.Secret, sa *v1.ServiceAccount) bool { 186 if secret.Type != v1.SecretTypeServiceAccountToken { 187 return false 188 } 189 190 name := secret.Annotations[v1.ServiceAccountNameKey] 191 uid := secret.Annotations[v1.ServiceAccountUIDKey] 192 if name != sa.Name { 193 // Name must match 194 return false 195 } 196 if len(uid) > 0 && uid != string(sa.UID) { 197 // If UID is specified, it must match 198 return false 199 } 200 201 return true 202 } 203 204 func GetOrCreateServiceAccount(coreClient v1core.CoreV1Interface, namespace, name string) (*v1.ServiceAccount, error) { 205 sa, err := coreClient.ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 206 if err == nil { 207 return sa, nil 208 } 209 if !apierrors.IsNotFound(err) { 210 return nil, err 211 } 212 213 // Create the namespace if we can't verify it exists. 214 // Tolerate errors, since we don't know whether this component has namespace creation permissions. 215 if _, err := coreClient.Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}); apierrors.IsNotFound(err) { 216 if _, err = coreClient.Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { 217 klog.Warningf("create non-exist namespace %s failed:%v", namespace, err) 218 } 219 } 220 221 // Create the service account 222 sa, err = coreClient.ServiceAccounts(namespace).Create(context.TODO(), &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}}, metav1.CreateOptions{}) 223 if apierrors.IsAlreadyExists(err) { 224 // If we're racing to init and someone else already created it, re-fetch 225 return coreClient.ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 226 } 227 return sa, err 228 }