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  }