...

Source file src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/authorize_utils.go

Documentation: kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1

     1  /*
     2  Copyright 2020 The CDI 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 v1beta1
    18  
    19  import (
    20  	"fmt"
    21  
    22  	authentication "k8s.io/api/authentication/v1"
    23  	authorization "k8s.io/api/authorization/v1"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/klog/v2"
    27  )
    28  
    29  func newCloneSourceHandler(dataVolume *DataVolume, dsGet dsGetFunc) (CloneSourceHandler, error) {
    30  	var pvcSource *DataVolumeSourcePVC
    31  	var snapshotSource *DataVolumeSourceSnapshot
    32  
    33  	if dataVolume.Spec.Source != nil {
    34  		if dataVolume.Spec.Source.PVC != nil {
    35  			pvcSource = dataVolume.Spec.Source.PVC
    36  		} else if dataVolume.Spec.Source.Snapshot != nil {
    37  			snapshotSource = dataVolume.Spec.Source.Snapshot
    38  		}
    39  	} else if dataVolume.Spec.SourceRef != nil && dataVolume.Spec.SourceRef.Kind == DataVolumeDataSource {
    40  		ns := dataVolume.Namespace
    41  		if dataVolume.Spec.SourceRef.Namespace != nil && *dataVolume.Spec.SourceRef.Namespace != "" {
    42  			ns = *dataVolume.Spec.SourceRef.Namespace
    43  		}
    44  		dataSource, err := dsGet(ns, dataVolume.Spec.SourceRef.Name)
    45  		if err != nil {
    46  			return CloneSourceHandler{}, err
    47  		}
    48  		if dataSource.Spec.Source.PVC != nil {
    49  			pvcSource = dataSource.Spec.Source.PVC
    50  		} else if dataSource.Spec.Source.Snapshot != nil {
    51  			snapshotSource = dataSource.Spec.Source.Snapshot
    52  		}
    53  	}
    54  
    55  	switch {
    56  	case pvcSource != nil:
    57  		return CloneSourceHandler{
    58  			CloneType:         pvcClone,
    59  			TokenResource:     tokenResourcePvc,
    60  			UserCloneAuthFunc: CanUserClonePVC,
    61  			SACloneAuthFunc:   CanServiceAccountClonePVC,
    62  			SourceName:        pvcSource.Name,
    63  			SourceNamespace:   pvcSource.Namespace,
    64  		}, nil
    65  	case snapshotSource != nil:
    66  		return CloneSourceHandler{
    67  			CloneType:         snapshotClone,
    68  			TokenResource:     tokenResourceSnapshot,
    69  			UserCloneAuthFunc: CanUserCloneSnapshot,
    70  			SACloneAuthFunc:   CanServiceAccountCloneSnapshot,
    71  			SourceName:        snapshotSource.Name,
    72  			SourceNamespace:   snapshotSource.Namespace,
    73  		}, nil
    74  	default:
    75  		return CloneSourceHandler{
    76  			CloneType: noClone,
    77  		}, nil
    78  	}
    79  }
    80  
    81  var (
    82  	tokenResourcePvc = metav1.GroupVersionResource{
    83  		Group:    "",
    84  		Version:  "v1",
    85  		Resource: "persistentvolumeclaims",
    86  	}
    87  
    88  	tokenResourceSnapshot = metav1.GroupVersionResource{
    89  		Group:    "snapshot.storage.k8s.io",
    90  		Version:  "v1",
    91  		Resource: "volumesnapshots",
    92  	}
    93  )
    94  
    95  type cloneType int
    96  
    97  const (
    98  	noClone cloneType = iota
    99  	pvcClone
   100  	snapshotClone
   101  )
   102  
   103  // CloneSourceHandler is a helper around determining the
   104  // correct way of authorizing a particular DataVolume
   105  // +k8s:deepcopy-gen=false
   106  // +k8s:openapi-gen=false
   107  type CloneSourceHandler struct {
   108  	CloneType         cloneType
   109  	TokenResource     metav1.GroupVersionResource
   110  	UserCloneAuthFunc UserCloneAuthFunc
   111  	SACloneAuthFunc   ServiceAccountCloneAuthFunc
   112  	SourceName        string
   113  	SourceNamespace   string
   114  }
   115  
   116  // CloneAuthResponse contains various response details
   117  // regarding authorizing a datavolume
   118  // +k8s:deepcopy-gen=false
   119  // +k8s:openapi-gen=false
   120  type CloneAuthResponse struct {
   121  	Handler CloneSourceHandler
   122  	Allowed bool
   123  	Reason  string
   124  }
   125  
   126  type createSarFunc func(*authorization.SubjectAccessReview) (*authorization.SubjectAccessReview, error)
   127  type dsGetFunc func(string, string) (*DataSource, error)
   128  
   129  // AuthorizationHelperProxy proxies calls to APIs used for DV authorization
   130  type AuthorizationHelperProxy interface {
   131  	CreateSar(*authorization.SubjectAccessReview) (*authorization.SubjectAccessReview, error)
   132  	GetNamespace(string) (*corev1.Namespace, error)
   133  	GetDataSource(string, string) (*DataSource, error)
   134  }
   135  
   136  // UserCloneAuthFunc represents a user clone auth func
   137  type UserCloneAuthFunc func(createSar createSarFunc, sourceNamespace, pvcName, targetNamespace string, userInfo authentication.UserInfo) (bool, string, error)
   138  
   139  // ServiceAccountCloneAuthFunc represents a serviceaccount clone auth func
   140  type ServiceAccountCloneAuthFunc func(createSar createSarFunc, pvcNamespace, pvcName, saNamespace, saName string) (bool, string, error)
   141  
   142  // CanUserClonePVC checks if a user has "appropriate" permission to clone from the given PVC
   143  func CanUserClonePVC(createSar createSarFunc, sourceNamespace, pvcName, targetNamespace string,
   144  	userInfo authentication.UserInfo) (bool, string, error) {
   145  	if sourceNamespace == targetNamespace {
   146  		return true, "", nil
   147  	}
   148  
   149  	var newExtra map[string]authorization.ExtraValue
   150  	if len(userInfo.Extra) > 0 {
   151  		newExtra = make(map[string]authorization.ExtraValue)
   152  		for k, v := range userInfo.Extra {
   153  			newExtra[k] = authorization.ExtraValue(v)
   154  		}
   155  	}
   156  
   157  	sarSpec := authorization.SubjectAccessReviewSpec{
   158  		User:   userInfo.Username,
   159  		Groups: userInfo.Groups,
   160  		Extra:  newExtra,
   161  	}
   162  
   163  	return sendSubjectAccessReviewsPvc(createSar, sourceNamespace, pvcName, sarSpec)
   164  }
   165  
   166  // CanServiceAccountClonePVC checks if a ServiceAccount has "appropriate" permission to clone from the given PVC
   167  func CanServiceAccountClonePVC(createSar createSarFunc, pvcNamespace, pvcName, saNamespace, saName string) (bool, string, error) {
   168  	if pvcNamespace == saNamespace {
   169  		return true, "", nil
   170  	}
   171  
   172  	user := fmt.Sprintf("system:serviceaccount:%s:%s", saNamespace, saName)
   173  
   174  	sarSpec := authorization.SubjectAccessReviewSpec{
   175  		User: user,
   176  		Groups: []string{
   177  			"system:serviceaccounts",
   178  			"system:serviceaccounts:" + saNamespace,
   179  			"system:authenticated",
   180  		},
   181  	}
   182  
   183  	return sendSubjectAccessReviewsPvc(createSar, pvcNamespace, pvcName, sarSpec)
   184  }
   185  
   186  // CanUserCloneSnapshot checks if a user has "appropriate" permission to clone from the given snapshot
   187  func CanUserCloneSnapshot(createSar createSarFunc, sourceNamespace, pvcName, targetNamespace string,
   188  	userInfo authentication.UserInfo) (bool, string, error) {
   189  	if sourceNamespace == targetNamespace {
   190  		return true, "", nil
   191  	}
   192  
   193  	var newExtra map[string]authorization.ExtraValue
   194  	if len(userInfo.Extra) > 0 {
   195  		newExtra = make(map[string]authorization.ExtraValue)
   196  		for k, v := range userInfo.Extra {
   197  			newExtra[k] = authorization.ExtraValue(v)
   198  		}
   199  	}
   200  
   201  	sarSpec := authorization.SubjectAccessReviewSpec{
   202  		User:   userInfo.Username,
   203  		Groups: userInfo.Groups,
   204  		Extra:  newExtra,
   205  	}
   206  
   207  	return sendSubjectAccessReviewsSnapshot(createSar, sourceNamespace, pvcName, sarSpec)
   208  }
   209  
   210  // CanServiceAccountCloneSnapshot checks if a ServiceAccount has "appropriate" permission to clone from the given snapshot
   211  func CanServiceAccountCloneSnapshot(createSar createSarFunc, pvcNamespace, pvcName, saNamespace, saName string) (bool, string, error) {
   212  	if pvcNamespace == saNamespace {
   213  		return true, "", nil
   214  	}
   215  
   216  	user := fmt.Sprintf("system:serviceaccount:%s:%s", saNamespace, saName)
   217  
   218  	sarSpec := authorization.SubjectAccessReviewSpec{
   219  		User: user,
   220  		Groups: []string{
   221  			"system:serviceaccounts",
   222  			"system:serviceaccounts:" + saNamespace,
   223  			"system:authenticated",
   224  		},
   225  	}
   226  
   227  	return sendSubjectAccessReviewsSnapshot(createSar, pvcNamespace, pvcName, sarSpec)
   228  }
   229  
   230  func sendSubjectAccessReviewsPvc(createSar createSarFunc, namespace, name string, sarSpec authorization.SubjectAccessReviewSpec) (bool, string, error) {
   231  	allowed := false
   232  
   233  	for _, ra := range getResourceAttributesPvc(namespace, name) {
   234  		sar := &authorization.SubjectAccessReview{
   235  			Spec: sarSpec,
   236  		}
   237  		sar.Spec.ResourceAttributes = &ra
   238  
   239  		klog.V(3).Infof("Sending SubjectAccessReview %+v", sar)
   240  
   241  		response, err := createSar(sar)
   242  		if err != nil {
   243  			return false, "", err
   244  		}
   245  
   246  		klog.V(3).Infof("SubjectAccessReview response %+v", response)
   247  
   248  		if response.Status.Allowed {
   249  			allowed = true
   250  			break
   251  		}
   252  	}
   253  
   254  	if !allowed {
   255  		return false, fmt.Sprintf("User %s has insufficient permissions in clone source namespace %s", sarSpec.User, namespace), nil
   256  	}
   257  
   258  	return true, "", nil
   259  }
   260  
   261  func sendSubjectAccessReviewsSnapshot(createSar createSarFunc, namespace, name string, sarSpec authorization.SubjectAccessReviewSpec) (bool, string, error) {
   262  	// Either explicitly allowed
   263  	sar := &authorization.SubjectAccessReview{
   264  		Spec: sarSpec,
   265  	}
   266  	explicitResourceAttr := getExplicitResourceAttributeSnapshot(namespace, name)
   267  	sar.Spec.ResourceAttributes = &explicitResourceAttr
   268  
   269  	klog.V(3).Infof("Sending SubjectAccessReview %+v", sar)
   270  
   271  	response, err := createSar(sar)
   272  	if err != nil {
   273  		return false, "", err
   274  	}
   275  
   276  	klog.V(3).Infof("SubjectAccessReview response %+v", response)
   277  
   278  	if response.Status.Allowed {
   279  		return true, "", nil
   280  	}
   281  
   282  	// Or both implicit conditions hold
   283  	for _, ra := range getImplicitResourceAttributesSnapshot(namespace, name) {
   284  		sar = &authorization.SubjectAccessReview{
   285  			Spec: sarSpec,
   286  		}
   287  		sar.Spec.ResourceAttributes = &ra
   288  
   289  		klog.V(3).Infof("Sending SubjectAccessReview %+v", sar)
   290  
   291  		response, err = createSar(sar)
   292  		if err != nil {
   293  			return false, "", err
   294  		}
   295  
   296  		klog.V(3).Infof("SubjectAccessReview response %+v", response)
   297  
   298  		if !response.Status.Allowed {
   299  			return false, fmt.Sprintf("User %s has insufficient permissions in clone source namespace %s", sarSpec.User, namespace), nil
   300  		}
   301  	}
   302  
   303  	return true, "", nil
   304  }
   305  
   306  func getResourceAttributesPvc(namespace, name string) []authorization.ResourceAttributes {
   307  	return []authorization.ResourceAttributes{
   308  		{
   309  			Namespace:   namespace,
   310  			Verb:        "create",
   311  			Group:       SchemeGroupVersion.Group,
   312  			Resource:    "datavolumes",
   313  			Subresource: DataVolumeCloneSourceSubresource,
   314  			Name:        name,
   315  		},
   316  		{
   317  			Namespace: namespace,
   318  			Verb:      "create",
   319  			Resource:  "pods",
   320  			Name:      name,
   321  		},
   322  	}
   323  }
   324  
   325  func getExplicitResourceAttributeSnapshot(namespace, name string) authorization.ResourceAttributes {
   326  	return authorization.ResourceAttributes{
   327  		Namespace:   namespace,
   328  		Verb:        "create",
   329  		Group:       SchemeGroupVersion.Group,
   330  		Resource:    "datavolumes",
   331  		Subresource: DataVolumeCloneSourceSubresource,
   332  		Name:        name,
   333  	}
   334  }
   335  
   336  func getImplicitResourceAttributesSnapshot(namespace, name string) []authorization.ResourceAttributes {
   337  	return []authorization.ResourceAttributes{
   338  		{
   339  			Namespace: namespace,
   340  			Verb:      "create",
   341  			Resource:  "pods",
   342  			Name:      name,
   343  		},
   344  		{
   345  			Namespace: namespace,
   346  			Verb:      "create",
   347  			Resource:  "pvcs",
   348  			Name:      name,
   349  		},
   350  	}
   351  }
   352  

View as plain text